JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库,JDBC是用Java语言向数据库发送SQL语句。
一,JDBC使用
首先依赖mysql驱动jar包:maven地址
public static void main(String[] args) throws Exception {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取MySQL连接
Connection connection = DriverManager.getConnection("jdbc:mysql://@localhost:3306/bobo,"root","密码");
// 创建SQL执行器
Statement statement = connection.createStatement();
// 执行查询语句
ResultSet resultSet = statement.executeQuery("select * from user");
// 解析结果
while (resultSet.next()){
System.out.println(resultSet.getString("name"));
}
// 关闭连接
statement.close();
connection.close();
}
注意:从mysql6开始,驱动的路径有更改,而且URL中要加入时区:
// 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取MySQL连接
Connection connection = DriverManager.getConnection("jdbc:mysql://@localhost:3306/bobo?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false","root","密码");
二,JDBC原理
1,JDBC基本原理
早期SUN公司的天才们想编写一套可以连接天下所有数据库的API,但是当他们刚刚开始时就发现这是不可完成的任务,因为各个厂商的数据库服务器差异太大了。
后来SUN开始与数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC,而各个厂商提供的,遵循了JDBC规范的,可以访问自己数据库的API被称之为驱动!
JDBC是接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接!每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。
JDBC中定义了一些接口:
- 驱动管理:DriverManager
- 连接接口:Connection,DatabasemetaData
- 语句对象接口:Statement,PreparedStatement,CallableStatement
- 结果集接口:ResultSet,ResultSetMetaData。
上面介绍了JDK中定义了JDBC接口规范,实现交给各个数据库厂商,那么JDK是怎么发现厂商提供的驱动呢?那么就要用到Java SPI机制了。
2,Java SPI机制
SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,
比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,
其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,
而Java的SPI机制可以为某个接口寻找服务实现。

如上图所示,接口对应的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。
SPI接口的定义在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中,实现位于独立的包中。
当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务实现的工具类是:java.util.ServiceLoader。
三,JDBC源码分析
DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时,不希望修改现有的代码,而希望通过一个简单的配置就可以达到效果。 在使用mysql驱动的时候,会有一个疑问,DriverManager是怎么获得某确定驱动类的?我们在运用Class.forName(“com.mysql.jdbc.Driver”)加载mysql驱动后,就会执行其中的静态代码把driver注册到DriverManager中,以便后续的使用。
Class.forName("com.mysql.cj.jdbc.Driver");
利用反射来加载mysql
驱动包中的Driver
类,然后会促发Driver
类的静态代码块的执行:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
Driver
中的静态代码块,把自己注册到JDK
的DriverManager
中,以供后面使用。
public class DriverManager {
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
}
将自己注册到驱动管理器的驱动列表中.
当获取连接的时候调用驱动管理器的连接方法从列表中获取:
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
DriverManager.getCollection()方法会遍历已经注册到系统中的驱动,调用驱动中相应的方法来得到真正的数据库连接。
自JDBC4.0开始,Class.forName(""),可以省略掉了
注释掉Class.forName("com.mysql.jdbc.Driver")
,直接去获取连接:
// 加载驱动,这步可以省略不写
// Class.forName("com.mysql.jdbc.Driver");
// 获取MySQL连接
Connection connection = DriverManager.getConnection("jdbc:mysql://@localhost:3306/bobo","root","zhao635481603");
当我们调用DriverManager.getConnection()
的时候,会促发DriverManager的静态代码块执行:
public class DriverManager {
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
}
可以看到其内部的静态代码块中有一个loadInitialDrivers
方法,loadInitialDrivers
用法用到了上文提到的spi工具类ServiceLoader
:
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
可以看到,DriverManager的静态代码块儿里会寻找 jdbc.drivers
这个系统变量,找到相应的驱动程序并使用Class.forName()来加载它,相当于我们之前写的Class.forName("com.mysql.jdbc.Driver");