JDBC(Java Database Connectivity)背景
在开发业务系统时,经常需要对数据进行持久化操作,将数据保存到硬盘中,比如对数据进行新增、查询、更新和删除。这时候就需要使用数据库,通常使用第三方数据库中间件来进行数据操作,应用程序只需要调用数据库中间件提供的api即可。由于市面上有很多数据库,如果提供的api不同,那么应用程序在使用不同的数据库时需要写不同的代码,带来维护的复杂性。这时候就需要一套标准,由不同的数据库厂家来共同这套遵守标准来提供实现给应用程序调用,jdbc便是这样一套标准。
JDBC api 简述
JDBC标准主要提供一套api规范,第三方应用通过实现JDBC的接口并提供一个jar包给应用程序调用,架构如下图
- 其中DriverManager负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
- 第三方数据库中间件需要实现Driver接口提供给DriverManager
- 应用程序通过调用JDBC API 来实现对数据库的操作,第三方数据库需要提供各自api的实现,其中主要包括:
- Connection:数据库连接,负责进行与数据库间的通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
- Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。
JDBC把对数据库的操作抽象出来,把建立数据库连接的行为聚合到Connection接口,对数据库操作的行为聚合到Statement接口中。
其中 connection 接口包含的方法:
statement包含的方法,都是与数据库操作相关的方法:
比如调用mysql提供的mysql-conector-java.jar进行查询:
public class MysqlJdbcTest {
@Test
public void jdbcQuery() throws Exception {
PooledDataSource ds = new PooledDataSource ();
// 设置mysql driver
ds.setDriver ("com.mysql.jdbc.Driver");
// 设置数据库url
ds.setUrl ("jdbc:mysql://localhost:3306/test");
ds.setUsername ("root");
ds.setPassword ("root");
// 获取一个Connection
Connection con = ds.getConnection ();
// 执行查询
exexuteQuery (con);
con.close ();
}
private static ResultSet exexuteQuery(Connection con) throws SQLException {
PreparedStatement st = con.prepareStatement ("select * from udp_record");
ResultSet rs = st.executeQuery ();
return rs;
}
}
建立数据库连接connection
上面的代码通过获取一个mysql的connection来进行查询,那么这个过程是如何实现的?为什么说数据库的连接是很耗费资源的呢?带着这个问题,使用wireshark试着抓一个本地的包来看一下这个调用过程,可以看到底层通过TCP协议+mysql二进制协议来执行,具体过程如下:
- 首先进行三次握手
- 接着发送一个Login Request
- 返回OK后进行query请求,看到一个query执行了7次request请求
- 最后使用con.close()方法关闭连接,发送一个RST 信号给服务器来中断连接
统计一下这条查询语句的时间为5.657848-5.496280=0.161568秒,如果使用一个线程来执行sql语句,假设2W日活,单用户单日平均执行10次查询,那么调用时间居然为9.976小时。如果使用多线程可以显著提高性能,同时使用线程池技术,来保证资源的合理利用(可以参考文章 线程池的前世今生)
比较好奇的是mysql是如果实现connection,并把参数中的sql语句发送给中间件的,这时候来跟一下源码:
首先通过prepareStatement来对sql语句进行前期的准备和校验工作
PreparedStatement st = con.prepareStatement ("select * from udp_record");
包括对ResultType校验,如果带有动态参数,还会对参数进行校验,并把sql转为nativeSql(作为mysql缓存主键),
和statementComment(用于执行数据库操作)
接下来调用statement的executeQuery()方法:
可以看到生成一个sendPacket缓冲区,
然后发送数据包给中间件,如果包含分页则设置分页参数
上面通过一条查询指令了解了对数据库的调用过程,那么如果有多个数据库中间件提供的jar包,他们是如何注册到JDBC的DriverManager中,又是如何获取到对应数据库的connection呢?
JDBC Driver加载机制
跟踪之前我们获取connection的代码,看到Conenection实际是从DriverManager中获得的
那么如果有多个数据库中间件提供的jar包,他们是如何注册到JDBC的DriverManager中,又是如何判断是否为对应数据库的connection呢?
接下来跟进DriverManager ,看到静态语句块:
跟进loadInitialDrivers()方法:
这段代码实际上使用spi机制依次执行META-INF包含的类的静态方法,
比如mysql 的JDBC中 Driver在进行加载到ClassLoader中会执行其中的静态代码块, 也就是注册到DriverManager中:
获取Connection对象时,迭代DriverManager中的Drivers ,分别对url前缀进行判断,如果满足条件则返回对应的Connection,可以看到在Postgresql提供的JDBC中的判断条件:
mysql提供的JDBC中判断URL的前缀是
通过上面分析,我们知道了需要调用jdbc的connection接口来对数据库进行连接,通过statement接口来进行CRUD,但是对数据进行处理还需要考虑数据的事务性,同时sql语句也不希望直接硬编码到代码中,接下来通过mybatis源码来进一步了解。
更多技术文章,请关注微信公众号: