Mybatis源码阅读之三——JDBC解析与Mybatis封装

【系列目录】
Mybatis源码阅读之一——工厂模式与SqlSessionFactory

Mybatis源码阅读之二——模板方法模式与Executor

【本文目录】

文章目录

  • 一. JDBC
    • Demo
    • JDBC实体解析
  • 二. mysql-connector
    • Driver
    • 获取Connection,Mysql驱动的负载策略
    • Statement
    • ResultSet
  • 二. Mybatis的封装
    • StatementHandler
    • ResultSetHandler
    • ResultHandler

学习Mybatis源码,不稍微了解下JDBC是不完整的,今天我们简单分析一下。

一. JDBC

JDBC(Java Database Connectivity)是JAVA语言下想要连接数据库不可或缺的组件,他是JAVA官方提供的一组接口,不同的数据库支持需要数据库各自的厂商实现。
比如,访问mysql需要实现了JDBC接口的mysql-connector。
jdbc的实现位于如下位置:
Mybatis源码阅读之三——JDBC解析与Mybatis封装_第1张图片

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 : 驱动接口,用于获取数据库连接,核心方法:Mybatis源码阅读之三——JDBC解析与Mybatis封装_第2张图片
  • DriverManager : 驱动管理接口,会将所有(加载的各数据库)的Driver保存在一个CopyOnWriteList中。
  • Connection:一个数据库连接的实体。
  • Statement:通过Connection创建,可以理解为一次具体的网络请求,支持一个完整的sql执行。
  • PreparedStatement : Statement的预编译版(预编译的性能需要实测),支持带?占位符的sql,防SQL注入说的就是它了。
  • CallableStatement : PreparedStatement的增强版,可以调用数据库的存储过程。
  • ResultSet: 一次网络请求所获取到的结果数据。

二. mysql-connector

Mybatis源码阅读之三——JDBC解析与Mybatis封装_第3张图片
了解了JDBC的大致构造,我们再从mysql入手,来看一下他是如何实现的。

Driver

JDBC Demo中,通过Class.forName加载Driver类时,会触发Driver内部的static代码块。
Mybatis源码阅读之三——JDBC解析与Mybatis封装_第4张图片
Mybatis源码阅读之三——JDBC解析与Mybatis封装_第5张图片

  • 红框一/二:此处的1是mysql-connector实现的Driver类,2才是JDBC原本的Driver接口。
  • 红框三:在static代码块中,使用JDBC提供的DriverManager进行注册,将mysql-connector的Driver实例加载。
  • 红框四: mysql-connector的Driver类本身没有实现connect等方法,而是放在了NonRegisteringDriver中。

获取Connection,Mysql驱动的负载策略

深入NonRegisteringDriver.connect(),会发现这里实现了JDBC URL的负载策略:
Mybatis源码阅读之三——JDBC解析与Mybatis封装_第6张图片
Mybatis源码阅读之三——JDBC解析与Mybatis封装_第7张图片

  • 第一步,验证业务系统给定的url是否正确
  • 第二步,获取url
  • 第三步,普通Connection的获取。
  • 第四步,故障转移的Connection处理获取,配置时直接配置两个地址:
  • 第五步,负载均衡的连接处理获取:
  • 第六步,主从分离的连接处理获取(第一个地址为主,后面为从):

获取Connection再往下的代码,最终使用了java.net.Socket。
Mybatis源码阅读之三——JDBC解析与Mybatis封装_第8张图片

Statement

ConnectionImpl 是Connection接口的mysql驱动实现。

  • ConnectionImpl.createStatement()
    Mybatis源码阅读之三——JDBC解析与Mybatis封装_第9张图片

  • StatementImpl.executeUpdate()
    StatementImpl 是Statement的mysql驱动实现。Mybatis源码阅读之三——JDBC解析与Mybatis封装_第10张图片

  • StatementImpl.executeUpdateInternal
    方法太长,我们摘取重要的。

    • 一开始对connection上锁。
      Mybatis源码阅读之三——JDBC解析与Mybatis封装_第11张图片
    • 实际执行时,使用NativeSession.execute方法Mybatis源码阅读之三——JDBC解析与Mybatis封装_第12张图片
  • NativeSession.execSQL()
    这里再深入就到了NativeProtocol,里面的逻辑基本就是对mysql协议数据的封装,有兴趣的朋友可以深入追一下
    Mybatis源码阅读之三——JDBC解析与Mybatis封装_第13张图片

  • 回到最上层的StatementImpl.executeUpdateInternal
    执行结果数据被记录在ResultSetInternalMethods
    Mybatis源码阅读之三——JDBC解析与Mybatis封装_第14张图片

ResultSet

从上面我们知道了Statement结果数据放在了ResultSetInternalMethods中。
而当我们获取Result时,获取的就是这个对象,他是ResultSet的子接口。Mybatis源码阅读之三——JDBC解析与Mybatis封装_第15张图片
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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

  • 简单看一下接口方法以及部分接口的实现类。
    Mybatis源码阅读之三——JDBC解析与Mybatis封装_第16张图片
    BaseStatementHandler
    Mybatis源码阅读之三——JDBC解析与Mybatis封装_第17张图片
    SimpleStatementHandler
    Mybatis源码阅读之三——JDBC解析与Mybatis封装_第18张图片
    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初始化的封装,有点工厂的味道:
    Mybatis源码阅读之三——JDBC解析与Mybatis封装_第19张图片

ResultSetHandler

Mybatis源码阅读之三——JDBC解析与Mybatis封装_第20张图片
看方法可知,他的作用就是将JDBC的ResultSet解析成List或游标,或是对存储过程的返回结果进行处理。

实现类DefaultResultSetHandler代码比较多,大家有兴趣可以深入一下。

ResultHandler

ResultSetHandler是对整个返回结果的处理,到达行这一层次的时候,ResultSetHandler调用了ResultHandler做处理。
Mybatis源码阅读之三——JDBC解析与Mybatis封装_第21张图片
Mybatis源码阅读之三——JDBC解析与Mybatis封装_第22张图片
入参的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。


今天的分享就到这里,原创不易,文章下方点个在看,作者更有动力!

你可能感兴趣的:(Mybatis源码阅读系列,JDBC,mybatis源码)