【系列目录】
Mybatis源码阅读之一——工厂模式与SqlSessionFactory
Mybatis源码阅读之二——模板方法模式与Executor
【本文目录】
文章目录
学习Mybatis源码,不稍微了解下JDBC是不完整的,今天我们简单分析一下。
一. JDBC
JDBC(Java Database Connectivity)是JAVA语言下想要连接数据库不可或缺的组件,他是JAVA官方提供的一组接口,不同的数据库支持需要数据库各自的厂商实现。
比如,访问mysql需要实现了JDBC接口的mysql-connector。
jdbc的实现位于如下位置:
Demo
下面给出一段通过JDBC进行查询的demo,大致逻辑:
- 加载驱动
- 获取Connection
- 生成Statement
- Statement连接DB Server,执行sql
- 数据写入Result中,再转换成自己需要的结构
public List<Map> executeSelectSql(String sql){
Connection conn = null;
Statement stmt = null;
List<Map> res;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = (Connection) DriverManager.getConnection(prop.getUrl(), prop.getUsername(), prop.getPassword());
stmt = (Statement) conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
res = convertList(rs);
rs.close();
stmt.close();
conn.close();
}finally {
// 关闭资源
if (stmt != null)
stmt.close();
if (conn != null)
conn.close();
}
return res;
}
private List<Map> convertList(ResultSet rs) throws SQLException{
List<Map> list = Lists.newArrayList();
ResultSetMetaData md = rs.getMetaData();
int columnCount = md.getColumnCount();
while (rs.next()) {
Map<String,Object> rowData = new LinkedHashMap<>();
for (int i = 1; i <= columnCount; i++) {
rowData.put(md.getColumnLabel(i), rs.getObject(i));
}
list.add(rowData);
}
return list;
}
JDBC实体解析
- Driver : 驱动接口,用于获取数据库连接,核心方法:
- DriverManager : 驱动管理接口,会将所有(加载的各数据库)的Driver保存在一个CopyOnWriteList中。
- Connection:一个数据库连接的实体。
- Statement:通过Connection创建,可以理解为一次具体的网络请求,支持一个完整的sql执行。
- PreparedStatement : Statement的预编译版(预编译的性能需要实测),支持带?占位符的sql,防SQL注入说的就是它了。
- CallableStatement : PreparedStatement的增强版,可以调用数据库的存储过程。
- ResultSet: 一次网络请求所获取到的结果数据。
二. mysql-connector
了解了JDBC的大致构造,我们再从mysql入手,来看一下他是如何实现的。
Driver
JDBC Demo中,通过Class.forName加载Driver类时,会触发Driver内部的static代码块。
- 红框一/二:此处的1是mysql-connector实现的Driver类,2才是JDBC原本的Driver接口。
- 红框三:在static代码块中,使用JDBC提供的DriverManager进行注册,将mysql-connector的Driver实例加载。
- 红框四: mysql-connector的Driver类本身没有实现connect等方法,而是放在了NonRegisteringDriver中。
获取Connection,Mysql驱动的负载策略
深入NonRegisteringDriver.connect(),会发现这里实现了JDBC URL的负载策略:
- 第一步,验证业务系统给定的url是否正确
- 第二步,获取url
- 第三步,普通Connection的获取。
- 第四步,故障转移的Connection处理获取,配置时直接配置两个地址:
- 第五步,负载均衡的连接处理获取:
- 第六步,主从分离的连接处理获取(第一个地址为主,后面为从):
获取Connection再往下的代码,最终使用了java.net.Socket。
Statement
ConnectionImpl 是Connection接口的mysql驱动实现。
-
ConnectionImpl.createStatement()
-
StatementImpl.executeUpdate()
StatementImpl 是Statement的mysql驱动实现。 -
StatementImpl.executeUpdateInternal
方法太长,我们摘取重要的。- 一开始对connection上锁。
- 实际执行时,使用NativeSession.execute方法
- 一开始对connection上锁。
-
NativeSession.execSQL()
这里再深入就到了NativeProtocol,里面的逻辑基本就是对mysql协议数据的封装,有兴趣的朋友可以深入追一下
-
回到最上层的StatementImpl.executeUpdateInternal
执行结果数据被记录在ResultSetInternalMethods
ResultSet
从上面我们知道了Statement结果数据放在了ResultSetInternalMethods中。
而当我们获取Result时,获取的就是这个对象,他是ResultSet的子接口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oL0xo8zr-1643522965475)(https://gitee.com/lele_fly/drawing-bed/raw/master/img/1643354176(1)].png)
- ResultSetImpl.getString()
我们挑一个方法看看:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-upwtIJqo-1643522965476)(https://gitee.com/lele_fly/drawing-bed/raw/master/img/1643354448(1)].png)- 第一步,检查连接是否中断,字段下表。
- 第二步,获取到Field。
- 第三步,在thisRow变量中取得实际值,
ResultSetImpl是一个类似Set的结构,有当前行的概念(thisRow),如果想要访问下一行的数据,需要调用next方法使thisRow移动.
- 第四步,判断字段值长度是否需要补充。
JDBC了解到这里应该差不多了,关于上一篇Executor的一些疑惑,相信读者朋友也能得到解答。
下面我们继续看一下在Mybatis中又对JDBC做了哪些封装。
二. Mybatis的封装
StatementHandler
-
简单看一下接口方法以及部分接口的实现类。
BaseStatementHandler
SimpleStatementHandler
StatementHandler实际上是对Statement做了一些封装,持有了Executor,Configuration等Mybatis自有变量,以及简化与ResultSet的交互等。 -
再来看一下StatementHandler及其子类的类图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a6qAblog-1643522965477)(https://gitee.com/lele_fly/drawing-bed/raw/master/img/1643358484(1)].png)
看这个类图是不是觉得十分眼熟?这和我们前文【Mybatis源码阅读之二——模板方法模式与Executor】中介绍的Executor一毛一样。
BaseStatementHandler及其子类实现了模板方法模式,三个子类分别与JDBC中的三种Statement对应。 -
RoutingStatementHandler与之前不同的是,他不是缓存的封装,而是对三种StatementHandler初始化的封装,有点工厂的味道:
ResultSetHandler
看方法可知,他的作用就是将JDBC的ResultSet解析成List或游标,或是对存储过程的返回结果进行处理。
实现类DefaultResultSetHandler代码比较多,大家有兴趣可以深入一下。
ResultHandler
ResultSetHandler是对整个返回结果的处理,到达行这一层次的时候,ResultSetHandler调用了ResultHandler做处理。
入参的ResultContext就是一行的数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zMagFwc-1643522965478)(https://gitee.com/lele_fly/drawing-bed/raw/master/img/1643362136(1)].png)
这里两个实现类,其中DefaultMapResultHandler值得一提,它是对@MapKey注解的处理逻辑。
@MapKey的用法:
我们希望Myabtis批量查询的结果不是一个List,而是一个Map的时候,在Mapper中定义接口如下:
@MapKey("id")
Map<String,Order> selectOrders();
此时Mybatis会通过DefaultMapResultHandler将我们定义的id取出来作为key,最终将结果转换成一个Map。
今天的分享就到这里,原创不易,文章下方点个在看,作者更有动力!