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

【系列目录】
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方法
  • 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。


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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值