JDBC,即Java数据库连接,是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC制定了统一访问各类关系数据库的标准接口,为各个数据库厂商提供了标准接口的实现。
JDBC操作数据库的步骤
1.加载数据库驱动;
2.建立数据库连接;
3.创建数据库操作对象;
4.定义操作的SQL语句并执行;
5.获取并操作结果集;
6.关闭资源:关闭顺序是结果集–>数据库操作对象–>连接。
JDBC API类图
在Java1.6之前我们加载注册数据库驱动以及获得数据库连接(如mysql)的代码步骤:
(1)Class.forName(“com.mysql.jdbc.Driver”);
(2)Connection con = DriverManager.getConnection(url, user, password);
但是在Java1.6开始,由于SPI机制的出现,不需要显示的调用Class.forName(“com.mysql.jdbc.Driver”);,就可以完成所有步骤。
JDBC核心源码分析
1.Driver
每个数据库驱动都必须实现的接口。
public interface Driver {
// 获取Connection,参数包括数据库的url和info,其中info至少包含user和password
Connection connect(String url, java.util.Properties info)
throws SQLException;
// 检测驱动是否是可以打开的连接
boolean acceptsURL(String url) throws SQLException;
// 获得驱动的属性
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
throws SQLException;
// 获得驱动的主要版本
int getMajorVersion();
// 获得驱动的次要版本
int getMinorVersion();
// 判断是否是一个真正的JDBC
boolean jdbcCompliant();
//------------------------- JDBC 4.1 -----------------------------------
// 返回父日志
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
2.DriverManager
管理一组JDBC驱动的基本服务。它的方法全是静态方法。
public class DriverManager {
// 已经注册的JDBC驱动列表
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
// 防止类被初始化
private DriverManager(){}
// 静态代码块,初始化加载驱动
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
......
}
现在我们以下述代码的方式获取数据库连接进行分析:
Connection con = DriverManager.getConnection(url, user, password);
为什么通过这句代码就可以完成数据库驱动的加载初始化呢。我们知道在调用一个类的静态方法时(getConnection())时会首先初始化该类,进而执行其静态代码块。
// 静态代码块,初始化加载驱动
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
静态代码块中只做了一个事情就是调用loadInitialDrivers()方法加载初始化驱动,该方法利用了ServiceLoader加载驱动,【ServiceLoader原理】。
// 初始化加载驱动核心代码
private static void loadInitialDrivers() {
String drivers;
try {
// AccessController.doPrivileged()是让不受信任的代码能够通过具有权限的中间方法调用需要权限的方法(如System.getProperty())。
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
// 获取系统的jdbc.drivers属性
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 通过ServiceLoader加载所有驱动,以便可以实例化
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
// 构造实例并进行初始化
while(driversIterator.hasNext()) {
// driversIterator.next(),驱动加载的核心
println(" Loading done by the java.util.ServiceLoader : "+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加载Driver的步骤顺序依次是:
- 通过SPI方式,读取 META-INF/services 下文件中的类名,使用线程上下文类加载器加载;
- 通过System.getProperty(“jdbc.drivers”)获取设置,然后通过系统类加载器加载。
driversIterator.next()中通过Class.forName(cn, true, loader).newInstance();对驱动进行初始化并实例化,放入缓存列表中。通常Class.forName(cn, true, loader)就可以触发初始化执行静态代码块。但是从mysql的官方文档中可以看到关于newInstance的相应解释:
The newInstance() call is a work around for some broken Java implementations. For some JVMs doing a forName does not call the static initializer - but creating a new instance does force the static initializer to be called.
这里,在初始化数据库驱动的时候会执行驱动类其各自的静态代码块,比如com.mysql.jdbc.Driver如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
可以看出MySQL的驱动Driver实现了Java的Driver,其中只有一个静态代码块,目的是注册驱动。通过Class.forName()初始化的时候执行静态代码化对驱动进行注册。静态代码块中直接调用了DriverManager的静态方法registerDriver()对驱动进行注册。
// 注册驱动
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
// 判断driver是否已经被注册到registeredDrivers,没有就注册进去
if(driver != null) {
// 将driver封装成DriverInfo【包装类】进行注册
registeredDrivers.addIfAbsent(new DriverInfo(driver));
} else {
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
至此,当获取一个数据库的连接时,会将全部的驱动进行注册。
// 注销驱动
public static synchronized void deregisterDriver(Driver driver) throws SQLException {
if (driver == null) {
return;
}
// 【native方法】获得调用此方法代码的类加载器
ClassLoader callerCL = DriverManager.getCallerClassLoader();
println("DriverManager.deregisterDriver: " + driver);
DriverInfo aDriver = new DriverInfo(driver);
if(registeredDrivers.contains(aDriver)) {
// 判断调用方是否有加载驱动程序的权限
if (isDriverAllowed(driver, callerCL)) {
registeredDrivers.remove(aDriver);
} else {
throw new SecurityException();
}
} else {
println("couldn't find driver to unload");
}
}
// 判断调用方的类加载器是否有加载驱动程序的权限
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
// 传入的classLoader为调用getConnetction的当前类加载器,从中寻找driver的Class对象
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}
// 只有同一个类加载器中的Class使用==比较时才会相等,此处就是校验用户注册Driver时该Driver所属的类加载器与调用时的是否同一个
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
接着,客户便可以通过DriverManager.getConnection()方法获得数据库连接,DriverManager提供了3个对外的getConnection方法。
// 参数包括数据库的url和info,其中info至少包含user和password
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
ClassLoader callerCL = DriverManager.getCallerClassLoader();
return (getConnection(url, info, callerCL));
}
// 参数包括数据库的url
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
ClassLoader callerCL = DriverManager.getCallerClassLoader();
return (getConnection(url, info, callerCL));
}
// 参数包括数据库的url,user和password
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
ClassLoader callerCL = DriverManager.getCallerClassLoader();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, callerCL));
}
//private方法,上述三个方法实际调用的都是下面的getConnection()方法
private static Connection getConnection(String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
// 如果类加载器为null,如果为空则通过Thread.currentThread().getContextClassLoader()去加载,以保证rt.jar包外的驱动可以被加载
synchronized(DriverManager.class) {
if(callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
// 遍历已经注册的JDBC驱动列表尝试获得一个连接
for(DriverInfo aDriver : registeredDrivers) {
// 如果调用方没有权限加载这个驱动则跳过
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
// 真正的获取connection的方法,通过Driver接口中的connect方法实现
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 (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中还有getDriver()和getDrivers()方法,用于返回特定的驱动以及所有调用方有权限加载的驱动列表。
3.Collection
代表与数据库的连接,提供Statement对象的创建以及数据库事务管理功能。
public interface Connection extends Wrapper, AutoCloseable {
// 创建Statement对象
Statement createStatement() throws SQLException;
// 创建PreparedStatement对象
PreparedStatement prepareStatement(String sql) throws SQLException;
// 创建CallableStatement对象
CallableStatement prepareCall(String sql) throws SQLException;
// 将SQL转换为本地执行SQL
String nativeSQL(String sql) throws SQLException;
// 设置自动提交
void setAutoCommit(boolean autoCommit) throws SQLException;
// 获得自动提交状态
boolean getAutoCommit() throws SQLException;
// 提交事务
void commit() throws SQLException;
// 回滚事务
void rollback() throws SQLException;
// 关闭连接
void close() throws SQLException;
// 判断连接是否关闭
boolean isClosed() throws SQLException;
......
// 还有很多新增的方法
}
4.Statement、PreparedStatement 和CallableStatement
- Statement:执行静态SQL
- PreparedStatement :执行预编译SQL
- CallableStatement:执行存储过程
这里举例PreparedStatement的几个重要方法:
public interface PreparedStatement extends Statement {
// 执行SQL查询语句,返回单个结果集
ResultSet executeQuery() throws SQLException;
// 执行SQL更新语句,如INSERT,UPDATE,DELETE
int executeUpdate() throws SQLException;
// 设置参数为NULL,必须指定参数的类型
void setNull(int parameterIndex, int sqlType) throws SQLException;
// 设置String类型的参数
void setString(int parameterIndex, String x) throws SQLException;
// 清空参数
void clearParameters() throws SQLException;
// 执行SQL组合语句,返回多个结果集
boolean execute() throws SQLException;
}
5.ResultSet
ResultSet是数据库查询结果返回的一种对象,是一个存储查询结果的对象,但结果集并不仅仅具有存储的功能,还具有操纵数据的功能,可以完成对数据的更新等。
public interface ResultSet extends Wrapper, AutoCloseable {
// 判断是否有下一个值
boolean next() throws SQLException;
// 关闭
void close() throws SQLException;
// 是否为空
boolean wasNull() throws SQLException;
// 获得第几columnIndex列的String类型数据
String getString(int columnIndex) throws SQLException;
.......
// 还有很多新增的方法
}