mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

一、JDBC执行过程

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

 1.1预编译的三种执行器

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

简单执行器(Statement)存在sql注入问题,发送一条一条静态sql语句(包含参数),传输体量比较大。

预处理执行器(PreparedStatement)可以防止sql注入问题,发送一条sql语句包含若干组参数,传输体量比较小。

存储过程处理器(CallableStatement)支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持。

1.2简单执行器与预处理执行器的区别?

1)简单执行器

  • 执行静态sql存在sql注入问题,不需要设置参数。
  • 相同的sql查询参数不同时会组装成不同参数的多条静态sql进行多次发送,从而多次编译多次执行。

2)预处理执行器

  • 执行动态sql可以防止sql注入问题,需要设置预编译参数。
  • 相同的sql查询参数不同时会组装成一条动态sql,多组不同参数进行发送,从而一次编译多次执行。

二、mybatis执行过程

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

 SqlSession设计采用的是门面模式-----方便调用。

这里主要讲述一下mybatis执行器

2.1mybatis的Executor体系

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

CachingExecutor类中是MyBatis关于二级缓存相关的逻辑。

BaseExecutor抽象类中是MyBatis关于一级缓存相关的逻辑。

SimpleExecutor、ReuseExecutor、BatchExecutor是继承自BaseExecutor具体的执行器类,具体执行数据库的增删改查。

1)SimpleExecutor简单执行器

简单执行每次都会创建一个新的预处理器(PrepareStatement),废话不多说上源码分析

这里先介绍一个类ConnectionLogger,该类是mybatis调用jdbc三种预处理器Statement进行交互的一个类(后面会介绍mybatis是如何调用jdbc框架的)。

主要代码如下:

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

2)ReuseExecutor可重用执行器

相同的sql只进行一次预处理,执行结果多次使用,废话不多说上源码分析

由以下源码可知:ReuseExecutor通过属性statementMap 缓存Statement来实现可重用性。

public class ReuseExecutor extends BaseExecutor {
    private final Map<String, Statement> statementMap = new HashMap();


   //判断如果Statement存在了直接从缓存中获取,若不存在创建并添加到缓存中
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        Statement stmt;
        if (this.hasStatementFor(sql)) {
            stmt = this.getStatement(sql);
            this.applyTransactionTimeout(stmt);
        } else {
            Connection connection = this.getConnection(statementLog);
            stmt = handler.prepare(connection, this.transaction.getTimeout());
            this.putStatement(sql, stmt);
        }
        handler.parameterize(stmt);
        return stmt;
    }
   //判断缓存是否存在且有效
    private boolean hasStatementFor(String sql) {
        try {//缓存存在且缓存未失效(会话未关闭)
            return this.statementMap.keySet().contains(sql) && !((Statement)this.statementMap.get(sql)).getConnection().isClosed();
        } catch (SQLException var3) {
            return false;
        }
    }

    private Statement getStatement(String s) {
        return (Statement)this.statementMap.get(s);
    }

    private void putStatement(String sql, Statement stmt) {
        this.statementMap.put(sql, stmt);
    }
}

3)BatchExecutor批处理执行器

BatchExecutor批处理执行器顾名思义支持批量处理,废话不多说上源码分析

由以下源码可知,BatchExecutor通过属性statementList存储Statement和属性batchResultList存储返回的结果集BatchResult(此时还没有真正填充结果集,只是new了一个对象),从而实现批量处理的功能。

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

 三、mybatis究竟是如何调用jdbc的呢(那我们来探究一下)?

在此之前我们要先大致介绍一下StatementHandler体系

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

 下面我们主要看一下RoutingStatementHandler类的源码

由以下源码可知该类是通过MappedStatement的属性statementType来判断mybatis执行时使用的哪个Handler

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

假设MyBatis使用的SimpleStatementHandler执行器,MappedStatement的statementType属性值为PREPARED,我们以此为例进行讲解MyBatis具体调用JDBC预处理器Statement的流程

首先MyBatis通过MappedStatement配置的statementType属性值PREPARED,由RoutingStatementHandler路由类分配得到PreparedStatementHandler,而后执行PreparedStatementHandler的instantiateStatement()方法具体代码如下:

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

 进一步走入ConnectionLogger代理类的invoke()方法,会根据上一步的方法名创建对应的PreparedStatement

mybatis源码解析之如何调用JDBC的预处理器Statement完成交互

 此处ConnectionLogger类是一个代理类,其主要功能是对instantiateStatement()方法中调用的prepareStatement、prepareCall、createStatement方法进行判断分别创建jdbc对应的Statement的代理日志类,完成mybatis与jdbc的交互。

以上各个Statement的代理日志类其核心功能还是Statement对象,只是通过代理模式增强添加了日志打印;由此,可以借鉴系统 与系统或接口与接口之间的交互在某种程度上可以使用代码模式进行交互以方便增强添加日志打印监控功能。