MyBatis师尊JDBC

1,362 阅读7分钟

原创 鲁班大叔 源码阅读网

文本由囚禁的鸟、鲁班大叔共同编写,预计阅读12分钟

概要

  1. 前言

  2. 重新理解JDBC

  3. 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,地位就像空气对于我们一样,如果有关系数据库厂商 不支持它,都懒的去看它,更别说用了。后果是什么你懂的。


3JDBC的实现

前面知道JDBC的厉害,不支持它后果很严重。那它具体是如何执行的呢?估计有些同学会认为是, 我们调用jdbc,jdbc在调用各数据库驱动。

这是错误的,jdbc并没有起到代理的作用,他做的是引导。事实上除了DriverManager之外,大部分它都是接口。这些接口由各数据库提供的Jar包(驱动包)实现

JDBC唯一要要做就是,在DrvierManager.getConnection() 时引导对应对应Driver来创建连接 。引导过程分为两步:

  1. 注册Driver实例
  2. 基于URL匹配对应Driver实例,然后创建连接。

注册Driver实例

DriverManager.registeredDrivers 中存储了,各数据库Driver实例。这些实例通常都是自己注册进去的。谁来触发这个注册动作呢?就是下面这行代码,熟悉吧

 Class.forName("com.mysql.jdbc.Driver");

我们刚学习JDBC时,总是对这行代码不解,以为是把这个类加载到ClassLoader当中去。其实它更实际意义是:通过静态块,注册自己实例到DriverManager。

1 package com.mysql.cj.jdbc
2 public class Driver  { 
3  static { 
4 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 }

完整代码详见:

DriverManager.loadInitialDrivers()


创建连接

连接都由Driver.connect() 创建,DriverManager遍历所有Driver实例,直到有Driver 能基于URL创建出connect为止。

1 Connection getConnection( 
2    String url, Properties info, Class<?> caller)  { 
3        for(DriverInfo aDriver : registeredDrivers) { 
4                Connection con = aDriver.driver.connect(url, info);    
5             if (con != null) {    
6                return (con);   
7              } 
8    }
9 }

注:篇幅所限只展现了关键源码,完整代码详见:DriverManager#getConnection()

创建连接后,接着声明Statement对象,但这时不在需要JDBC介入了,因为Connection 是驱动包自己实现。


核心流程

注:以下为JDBC标准流程和API讲述,如果你已非常熟悉可跳过,直接看MyBatis整合部分。

JDBC标准流程分为以下步骤:

  1. 建立连接 (Connection)
  2. 创建声明(Statement)
  3. 执行SQL获取结果集(ResultSet)
  4. 处理结果集
  5. 关闭资源


1获取连接

通过DriverManager的getConnection来创建一个连接,传入url、user、password等参数。

1 Connection connection = DriverManager
2 .getConnection(url, user, password);

关于Connection底层实现,有几点大家可以简单知晓下:
  1. 连接的创建,最终是数据驱动创建的,DriverManager只是引导。

  2. 连接指和数据库建立远程通信,使用Socket进行字节传输,并有一套自己的报文协议。

  3. 连接是不可以跨线程,会导致传输数据和状态混乱。

关于连接实现,不同数据库驱动实现方式都不一样,不好展开细讲,感兴趣的可以去研究一下MySql驱动。建议从协议报文开始


2创建Statement

Statement用于执行具体的SQL,并返回结果集。他有三种类型:

  1. Statement(基础):执行静态Sql、批处理、设置加载行数
  2. PreparedStatement(预处理):预编译参数,可防Sql注入
  3. 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的调用分布在四大组件上:

  1. **执行器:**操作Connection 进行连接、提交、回滚、关闭。
  2. **Sql处理器:**创建Sql声明(Statement)、设置超时、设置FetchSize、关闭Statement
  3. **参数处理器:**为预处理器(PreparedStatement)设置参数
  4. **结果集处理器:**读取结果集(ResultSet)、关闭结果集。

可以看出,MyBatis在对JDBC使用上,每个组件分工非常明确。各自都处理JDBC上功能。前期你对MyBatis组件比较陌生,可以对照JDBC组件进行记忆。


总结:

  1. JDBC是一套标准,好处可降低学习成本,减少编码量。
  2. 各数据库是这套标准的具体实现。
  3. 在执行层面,JDBC唯一作用是引导,而非代理。
  4. JDBC 基于DriverManger来指定驱动创建连接,前提是要自己先注册进去。
  5. MyBatis四大核心组件均有操作JDBC,并且分工明确。

源码阅读网 发起了一个读者讨论MyBatis、JDBC、数据库驱动三者是什么关系?你能用现实世界中的例子表述出来吗?精选讨论内容

感谢点赞