【系列目录】
Mybatis源码阅读之一——工厂模式与SqlSessionFactory
Mybatis源码阅读之二——模板方法模式与Executor
【本文目录】
JDBC(Java Database Connectivity)是JAVA语言下想要连接数据库不可或缺的组件,他是JAVA官方提供的一组接口,不同的数据库支持需要数据库各自的厂商实现。
比如,访问mysql需要实现了JDBC接口的mysql-connector。
jdbc的实现位于如下位置:
下面给出一段通过JDBC进行查询的demo,大致逻辑:
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的大致构造,我们再从mysql入手,来看一下他是如何实现的。
JDBC Demo中,通过Class.forName加载Driver类时,会触发Driver内部的static代码块。
深入NonRegisteringDriver.connect(),会发现这里实现了JDBC URL的负载策略:
获取Connection再往下的代码,最终使用了java.net.Socket。
ConnectionImpl 是Connection接口的mysql驱动实现。
StatementImpl.executeUpdate()
StatementImpl 是Statement的mysql驱动实现。
StatementImpl.executeUpdateInternal
方法太长,我们摘取重要的。
NativeSession.execSQL()
这里再深入就到了NativeProtocol,里面的逻辑基本就是对mysql协议数据的封装,有兴趣的朋友可以深入追一下
回到最上层的StatementImpl.executeUpdateInternal
执行结果数据被记录在ResultSetInternalMethods
从上面我们知道了Statement结果数据放在了ResultSetInternalMethods中。
而当我们获取Result时,获取的就是这个对象,他是ResultSet的子接口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oL0xo8zr-1643522965475)(https://gitee.com/lele_fly/drawing-bed/raw/master/img/1643354176(1)].png)
ResultSetImpl是一个类似Set的结构,有当前行的概念(thisRow),如果想要访问下一行的数据,需要调用next方法使thisRow移动.
JDBC了解到这里应该差不多了,关于上一篇Executor的一些疑惑,相信读者朋友也能得到解答。
下面我们继续看一下在Mybatis中又对JDBC做了哪些封装。
简单看一下接口方法以及部分接口的实现类。
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初始化的封装,有点工厂的味道:
看方法可知,他的作用就是将JDBC的ResultSet解析成List或游标,或是对存储过程的返回结果进行处理。
实现类DefaultResultSetHandler代码比较多,大家有兴趣可以深入一下。
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。
今天的分享就到这里,原创不易,文章下方点个在看,作者更有动力!