MyBatis师尊JDBC
概要:
- 前言
- 重新理解JDBC
- MyBatis与JDBC整合
而我们现在已经很少直接使用JDBC了。为了让大家更好的看懂MyBatis源码,很有必要在温故知新一下JDBC。
重新理解JDBC
JDBC全称Java Database Connectivity,它是JAVA操作数据库的一套标准。通过它去操作各数据库。
1.什么是标准
现实中,我们对于标准的理解,通常指统一的尺寸,如室内门的标准是86cm*205cm,要不是这个尺寸那就是非标门,如果你家的门是非标门,那就祈祷它不要坏。因为更换非标门,同样品质要出更多的钱,得定制。这就是标准的好处,兼容性高,更要的是可以为你省钱。
为啥我这么清楚?“非标门"、"不要坏" 我家门刚好凑齐。
2.支持JDBC的后果
如果有数据库就是不服JDBC,不支持其标准,会怎么样?
那我们操作这个数据库就得”定制“代码,直接访问它的驱动。接下来我们演示一下,不用JDBC 怎么操作MySql:
1 //1.创建Driver
2 Driver driver = new com.mysql.cj.jdbc.Driver();
3 //2.获取连接
4 JdbcConnection conn= driver.connect(url,properties)
5 //3.预编译SQL
6 JdbcPreparedStatement sta=conn.prepareStatement(sql);
7 //4.执行SQL
8 sta.executeQuery()
写这种代码,不仅要去学习它的文档,还加大了编码量。所以如果有客户让我们写这种代码,那得加钱。
所以JDBC对于JAVA,地位就像空气对于我们一样,如果有关系数据库厂商 不支持它,都懒的去看它,更别说用了。后果是什么你懂的。
3.JDBC的实现
前面知道JDBC的历害,不支持它后果很严得,它具体是如何执行的呢?估计有些同学会认为是, 我们调用jdbc,jdbc在调用各数据库驱动。
这是错误的,jdbc并没有起到代理的作用,他做的是引导。事实上除了DriverManager之外,大部分它都是接口。 这些接口由各数据库提供的Jar包(驱动包)实现,比如在MySql中,Connection的实现是com.mysql.cj.jdbc.ConnectionImpl。
JDBC唯一要要做就是,在DrvierManager.getConnection() 时引导对应对应Driver来创建连接 。引导过程分为两步:
- 注册Driver实例
- 基于URL匹配对应Driver实例,然后创建连接。
注册Driver实例
DriverManager.registeredDrivers 中存储了,各数据库Driver实例。这些实例通常都是自己注册进去的。谁来触发这个注册动作呢?就是下面这行代码,熟悉吧
1 Class.forName("com.mysql.jdbc.Driver");
我们刚学习JDBC时,总是对这行代码不解,以为是把这个类加载到ClassLoader当中去。其实它更实际意义是:通过静态块,注册自己实例到DriverManager。
1 package com.mysql.cj.jdbc
2 public class Driver {
3 static {
4 java.sql.DriverManager.registerDriver(new Driver());
5 }
6 }
SPI 装载
随着SPI机制的引入,我们已经不需要在手动Class.forName了。在DriverManager初始化时,会利用SPI中的 ServiceLoader 装载所有Driver类。依据是 xxx.jar/META-INF/services/java.sql.Driver文件,该文件指明了要加载Driver类。一但该类加载,上述静态块中注册逻辑就会执行。
说明:xxx.jar代指数据库驱动包,如mysql-connector-java-8.0.17.jar
以下为DriverManager利用SPI加载Driver的代码:
1 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
2 Iterator<Driver> driversIterator = loadedDrivers.iterator();
3 while(driversIterator.hasNext()) {
4 driversIterator.next();
5 }
创建连接
连接都由Driver.connect() 创建,DriverManager遍历所有Driver实例,直到有Driver 能基于URL创建出connect为止。
1 Connection getConnection(
2 String url, Properties info, Class<?> caller) {
3
4 for(DriverInfo aDriver : registeredDrivers) {
5 Connection con = aDriver.driver.connect(url, info);
6 if (con != null) {
7 return (con);
8 }
9 }
10 }
注:篇幅所限只展现了关键源码,完整代码详见:java.sql.DriverManager#getConnection()
创建连接后,接着声明Statement对象,但这时不在需要JDBC介入了,因为Connection 是驱动包自己实现。
核心流程
注:以下为JDBC标准流程和API讲述,如果你已非常熟悉可跳过,直接看MyBatis整合部分。
JDBC标准流程分为以下步骤:
- 建立连接 (Connection)
- 创建声明(Statement)
- 执行SQL获取结果集(ResultSet)
- 处理结果集
- 关闭资源
1.获取连接
通过DriverManager的getConnection来创建一个连接,传入url、user、password等参数。
1 Connection connection = DriverManager
2 .getConnection(url, user, password);
关于Connection底层实现,有几点大家可以简单知晓下:
- 连接的创建,最终是数据驱动创建的,DriverManager只是引导。
- 连接指和数据库建立远程通信,使用Socket进行字节传输,并有一套自己的报文协议。
- 连接是不可以跨线程,并发使用的,会导致传输数据和状态混乱。
关于连接实现,不同数据库驱动实现方式都不一样,不好展开细讲,
感兴趣的可以去研究一下MySql驱动。 建议从协议报文开始
2.创建Statement
Statement用于执行具体的SQL,并返回结果集。他有三种类型:
- Statement(基础):执行静态Sql、批处理、设置加载行数
- PreparedStatement(预处理):预编译参数,可防Sql注入
- CallableStatement(存储过程):支持对存储过程调用,基于出参,可处理多份结果集三者是如下图表示的继承关系,即后者拥有前者的所有功能。
结果集参数
Statement 由Connection 创建,创建方法相信大家都清楚,这里介绍一下创建过程中的三个参数、它们都跟结果集相关:
参数 | 值 | 描述 |
resultSetType 结果集是否可前后滚动 | TYPE_FORWARD_ONLY | 结果集的读取只能步步向前,不可前后滚动 |
TYPE_SCROLL_INSENSITIVE | 可前后滚动,并且对修改不敏感 | |
TYPE_SCROLL_SENSITIVE | 可前后滚动,对修改敏感 | |
resultSetConcurrency 结果集是否可修改 | CONCUR_READ_ONLY | 只读结果集 |
CONCUR_UPDATABLE | 可读写结果集,即可通过结果集修改表数据 | |
resultSetHoldability 结果集保持方式 | HOLD_CURSORS_OVER_COMMIT | 在连接提交后,结果集仍然可用 |
CLOSE_CURSORS_AT_COMMIT | 在提交或回滚后,自动关闭结果集 |
注:表中常量均来自 java.sql.ResultSet
3.执行Sql
指发送Sql语句到数据库,并返回结果集。相关可以分成三类:
1.查询:
执行 executeQuery立即返回结果集,如果是execute需要在调用getResultSet()获取结果集。
2.修改
执行executeUpdate返回影响行,通过execute执行,需要在调用getUpdateCount()获取。
注:修改代指:增、删、改
3.批处理
批处理分为两步。
- 第一步:准备,Statement.addBatch(sql) 添加一个静态SQl,PreparedStatement.addBatch() 添加一个预处理SQL。
- 第二步:执行,通过executeBatch() 一次性发送出去,然后获取执行结果。
注意:批处理只针对修改操作。
4.结果处理
执行完SQL后,其结果会放置于内存当中,通过ResultSet 即可获取。这数据分为两类,
第一类:元数据,包含返回的列数量、列名称、列类型。通过getMetaData()获取。
第二类:具体结果,它是一个二维数据,通过next()依次读取行,在getXXX 获取当前行中的列。
1 while (rs.next()) {
2 String name = rs.getString("name");
3 // ...
4 }
5.释放资源
业务处理完毕之后我们需要及时关闭Statement和Connection。
1 rs.close();
2 stmt.close();
3 connection.close();
MyBatis整合
前面讲述了JDBC和各数据库驱动的关系,一个定义标准,一个实现标准。接下来我们在回到MyBatis。它是如何使用JDBC的?关键代码在哪里?
其对JDBC的调用分布在四大组件上:
- 执行器:操作Connection 进行连接、提交、回滚、关闭。
- Sql处理器:创建Sql声明(Statement)、设置超时、设置FetchSize、关闭Statement
- 参数处理器:为预处理器(PreparedStatement)设置参数
- 结果集处理器:读取结果集(ResultSet)、关闭结果集。
可以看出,MyBatis在对JDBC使用上,每个组件分工非常明确。各自都处理JDBC上功能。前期你对MyBatis组件比较陌生,可以对照JDBC组件进行记忆。
总结:
- JDBC是一套标准,好处可降低学习成本,减少编码量。
- 各数据库是这套标准的具体实现。
- 在执行层面,JDBC唯一作用是引导,而非代理。
- JDBC 基于DriverManger来指定驱动创建连接,前提是驱要自己先注册进去。
- MyBatis在四在核心组件均有操作JDBC,并且分工明确。
最后的讨论:
MyBatis、JDBC、数据库驱动三者是什么关系?你能用现实世界中的例子表述出来吗?