MyBatis 源码分析 - SQL执行过程(二)之 StatementHandler

参考 知识星球芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄

该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址Mybatis-Spring 源码分析 GitHub 地址Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

该系列其他文档请查看:《精尽 MyBatis 源码分析 - 文章导读》

MyBatis的SQL执行过程

在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了

那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:

MyBatis中SQL执行的整体过程如下图所示:

SQLExecuteProcess

在 SqlSession 中,会将执行 SQL 的过程交由Executor执行器去执行,过程大致如下:

  1. 通过DefaultSqlSessionFactory创建与数据库交互的 SqlSession “会话”,其内部会创建一个Executor执行器对象
  2. 然后Executor执行器通过StatementHandler创建对应的java.sql.Statement对象,并通过ParameterHandler设置参数,然后执行数据库相关操作
  3. 如果是数据库更新操作,则可能需要通过KeyGenerator先设置自增键,然后返回受影响的行数
  4. 如果是数据库查询操作,则需要将数据库返回的ResultSet结果集对象包装成ResultSetWrapper,然后通过DefaultResultSetHandler对结果集进行映射,最后返回 Java 对象

上面还涉及到一级缓存二级缓存延迟加载等其他处理过程

SQL执行过程(二)之StatementHandler

在上一篇文档中,已经详细地分析了在MyBatis的SQL执行过程中,SqlSession会话将数据库操作交由Executor执行器去完成,实际上需要通过StatementHandler创建相应的Statement对象,并做一些准备工作,然后通过Statement执行数据库操作,查询结果则需要通过ResultSetHandler对结果集进行映射转换成Java对象,那么接下来我们先来看看StatementHandler到底做哪些操作

StatementHandler接口的实现类如下图所示:

StatementHandler
  • org.apache.ibatis.executor.statement.RoutingStatementHandler:实现StatementHandler接口,装饰器模式,根据Statement类型创建对应的StatementHandler对象,所有的方法执行交由该对象执行

  • org.apache.ibatis.executor.statement.BaseStatementHandler:实现StatementHandler接口,提供骨架方法,指定的几个抽象方法交由不同的子类去实现

  • org.apache.ibatis.executor.statement.SimpleStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.Statement进行数据库操作

  • org.apache.ibatis.executor.statement.PreparedStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.PreparedStatement进行数据库操作(默认)

  • org.apache.ibatis.executor.statement.CallableStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.CallableStatement进行数据库操作,用于存储过程

我们先回顾一下StatementHandler是在哪里被创建的,可以在《SQL执行过程(一)之Executor》SimpleExecutor小节中有讲到,创建的是RoutingStatementHandler对象

StatementHandler

org.apache.ibatis.executor.statement.StatementHandler:Statement处理器接口,代码如下:

public interface StatementHandler { /** * 准备操作,可以理解成创建 Statement 对象 * * @param connection Connection 对象 * @param transactionTimeout 事务超时时间 * @return Statement 对象 */ Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; /** * 设置 Statement 对象的参数 * * @param statement Statement 对象 */ void parameterize(Statement statement) throws SQLException; /** * 添加 Statement 对象的批量操作 * * @param statement Statement 对象 */ void batch(Statement statement) throws SQLException; /** * 执行写操作 * * @param statement Statement 对象 * @return 影响的条数 */ int update(Statement statement) throws SQLException; /** * 执行读操作 * * @param statement Statement 对象 * @param resultHandler ResultHandler 对象,处理结果 * @param <E> 泛型 * @return 读取的结果 */ <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; /** * 执行读操作,返回 Cursor 对象 * * @param statement Statement 对象 * @param <E> 泛型 * @return Cursor 对象 */ <E> Cursor<E> queryCursor(Statement statement) throws SQLException; /** * @return BoundSql 对象 */ BoundSql getBoundSql(); /** * @return ParameterHandler 对象 */ ParameterHandler getParameterHandler(); }

每个方法可以根据注释先理解它的作用,在实现类中的会讲到

RoutingStatementHandler

org.apache.ibatis.executor.statement.RoutingStatementHandler:实现StatementHandler接口,采用装饰器模式,在初始化的时候根据Statement类型,创建对应的StatementHandler对象,代码如下:

public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 根据不同的类型,创建对应的 StatementHandler 实现类 switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } } }
  • 在构造函数中初始化delegate委托对象,根据MappedStatement(每个SQL对应的对象)的statementType类型,创建对应的StatementHandler实现类

  • 其余所有的方法都是直接交由delegate去执行的,这里就不列出来了,就是实现StatementHandler接口的方法

回顾到《MyBatis初始化(二)之加载Mapper接口与XML映射文件》中的XMLStatementBuilder小节,在parseStatementNode方法中的第10步如下:

StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

所以说statementType的默认值为PREPARED,委托对象也就是PreparedStatementHandler类型

BaseStatementHandler

org.apache.ibatis.executor.statement.BaseStatementHandler:实现StatementHandler接口,提供骨架方法,指定的几个抽象方法交由不同的子类去实现

构造方法

public abstract class BaseStatementHandler implements StatementHandler { /** * 全局配置 */ protected final Configuration configuration; /** * 实例工厂 */ protected final ObjectFactory objectFactory; /** * 类型处理器注册表 */ protected final TypeHandlerRegistry typeHandlerRegistry; /** * 执行结果处理器 */ protected final ResultSetHandler resultSetHandler; /** * 参数处理器,默认 DefaultParameterHandler */ protected final ParameterHandler parameterHandler; /** * 执行器 */ protected final Executor executor; /** * SQL 相关信息 */ protected final MappedStatement mappedStatement; /** * 分页条件 */ protected final RowBounds rowBounds; /** * SQL 语句 */ protected BoundSql boundSql; protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { this.configuration = mappedStatement.getConfiguration(); this.executor = executor; this.mappedStatement = mappedStatement; this.rowBounds = rowBounds; this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); this.objectFactory = configuration.getObjectFactory(); // <1> 如果 boundSql 为空,更新数据库的操作这里传入的对象会为 null if (boundSql == null) { // issue #435, get the key before calculating the statement // <1.1> 生成 key,定义了 <selectKey /> 且配置了 order="BEFORE",则在 SQL 执行之前执行 generateKeys(parameterObject); // <1.2> 创建 BoundSql 对象 boundSql = mappedStatement.getBoundSql(parameterObject); } this.boundSql = boundSql; // <2> 创建 ParameterHandler 对象,默认为 DefaultParameterHandler // PreparedStatementHandler 实现的 parameterize 方法中需要对参数进行预处理,进行参数化时需要用到 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); // <3> 创建 DefaultResultSetHandler 对象 this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); } }

关于它的属性可以根据注释进行理解

  1. 如果入参中的boundSqlnull,则需要进行初始化,可以会看到SimpleExecutor中执行数据库的更新操作时,传入的boundSqlnull,数据库的查询操作才会传入该对象的值

    1. 调用generateKeys(Object parameter)方法,根据配置的KeyGenerator对象,在SQL执行之前执行查询操作获取值,设置到入参对象对应属性中,代码如下:

      protected void generateKeys(Object parameter) { /* * 获得 KeyGenerator 对象 * 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象 * 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象 * 否则为 NoKeyGenerator 对象 */ KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); ErrorContext.instance().store(); // 前置处理,创建自增编号到 parameter 中 keyGenerator.processBefore(executor, mappedStatement, null, parameter); ErrorContext.instance().recall(); }

      只有配置的<selectKey />标签才有前置处理,这就是为什么数据库的更新操作传入的boundSqlnull的原因,因为入参中有的属性值可能需要提前生成一个值(执行配置的SQL语句),KeyGenerator会在后续讲到😈

    2. 通过MappedStatement对象根据入参获取BoundSql对象,在《MyBatis初始化(四)之SQL初始化(下)》中的SqlSource小节中有讲到这个方法,如果是动态SQL则需要进行解析,获取到最终的SQL,替换成?占位符

  2. 创建ParameterHandler对象,用于对参数进行预处理,默认为DefaultParameterHandler,这个也在《MyBatis初始化(四)之SQL初始化(下)》中有讲过

    可以看到Configuration的newParameterHandler方法:

    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { // 创建 ParameterHandler 对象 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); // 应用插件 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }
  3. 创建ResultSetHandler,用于返回结果的映射,默认为DefaultResultSetHandler,这个映射过程非常复杂,会有单独一篇文档进行分析😈

    可以看到Configuration的newResultSetHandler方法:

    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { // 创建 DefaultResultSetHandler 对象 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); // 应用插件 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; }

prepare方法

创建Statement对象,做一些初始化工作,代码如下:

@Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { // <1> 创建 Statement 对象 statement = instantiateStatement(connection); // <2> 设置执行和事务的超时时间 setStatementTimeout(statement, transactionTimeout); // <3> 设置 fetchSize,为驱动的结果集获取数量(fetchSize)设置一个建议值 setFetchSize(statement); return statement; } catch (SQLException e) { // 发生异常,进行关闭 closeStatement(statement); throw e; } catch (Exception e) { // 发生异常,进行关闭 closeStatement(statement); throw new ExecutorException("Error preparing statement. Cause: " + e, e); } }
  1. 创建 Statement 对象,调用instantiateStatement(Connection connection)抽象方法,交由不同的子类去实现

  2. 设置执行和事务的超时时间,调用setStatementTimeout(Statement stmt, Integer transactionTimeout)方法,如下:

    protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException { Integer queryTimeout = null; // 获得 queryTimeout if (mappedStatement.getTimeout() != null) { queryTimeout = mappedStatement.getTimeout(); } else if (configuration.getDefaultStatementTimeout() != null) { queryTimeout = configuration.getDefaultStatementTimeout(); } // 设置执行的超时时间 if (queryTimeout != null) { stmt.setQueryTimeout(queryTimeout); } // 设置事务超时时间 StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout); }
  3. 设置 fetchSize,为驱动的结果集获取数量(fetchSize)设置一个建议值(无默认值),调用setFetchSize(Statement stmt)方法,如下:

    protected void setFetchSize(Statement stmt) throws SQLException { // 获得 fetchSize 配置 Integer fetchSize = mappedStatement.getFetchSize(); if (fetchSize != null) { stmt.setFetchSize(fetchSize); return; } // 获得 fetchSize 的默认配置 Integer defaultFetchSize = configuration.getDefaultFetchSize(); if (defaultFetchSize != null) { stmt.setFetchSize(defaultFetchSize); } }
  4. 发生任何异常都会关闭 Statement 对象

SimpleStatementHandler

org.apache.ibatis.executor.statement.SimpleStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.Statement进行数据库操作,部分代码如下:

public class SimpleStatementHandler extends BaseStatementHandler { public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql); } @Override public void batch(Statement statement) throws SQLException { String sql = boundSql.getSql(); // 添加到批处理 statement.addBatch(sql); } @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); // <1> 执行查询 statement.execute(sql); // <2> 处理返回结果 return resultSetHandler.handleResultSets(statement); } @Override public <E> Cursor<E> queryCursor(Statement statement) throws SQLException { String sql = boundSql.getSql(); statement.execute(sql); return resultSetHandler.handleCursorResultSets(statement); } @Override protected Statement instantiateStatement(Connection connection) throws SQLException { if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) { // 创建Statement对象 return connection.createStatement(); } else { return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } } @Override public void parameterize(Statement statement) { // N/A } }
  • 上面的方法都很简单,都是直接通过Statement对象执行数据库操作

  • 在查询方法中,需要通过resultSetHandler对结果集进行映射,返回对应的Java对象

update方法

执行数据库更新操作,方法如下:

@Override public int update(Statement statement) throws SQLException { String sql = boundSql.getSql(); Object parameterObject = boundSql.getParameterObject(); /* * 获得 KeyGenerator 对象 * 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象 * 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象 * 否则为 NoKeyGenerator 对象 */ KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); int rows; if (keyGenerator instanceof Jdbc3KeyGenerator) { // 如果是 Jdbc3KeyGenerator 类型 // <1.1> 执行写操作,设置返回自增键,可通过 getGeneratedKeys() 方法获取 statement.execute(sql, Statement.RETURN_GENERATED_KEYS); // <1.2> 获得更新数量 rows = statement.getUpdateCount(); // <1.3> 执行 keyGenerator 的后置处理逻辑,也就是对我们配置的自增键进行赋值 keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else if (keyGenerator instanceof SelectKeyGenerator) { // 如果是 SelectKeyGenerator 类型 // <2.1> 执行写操作 statement.execute(sql); // <2.2> 获得更新数量 rows = statement.getUpdateCount(); // <2.3>执行 keyGenerator 的后置处理逻辑,也就是对我们配置的自增键进行赋值 keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else { // <3.1> 执行写操作 statement.execute(sql); // <3.2> 获得更新数量 rows = statement.getUpdateCount(); } return rows; }

为什么数据库更新操作的方法需要做这么多处理,其实目的就一个,支持用户配置的自增键,设置到入参中

BaseStatementHandler的构造方法已经有过KeyGenerator前置处理了,那里是在SQL执行之前,执行查询操作获取值,设置到入参对象对应属性中

而这里需要做的就是在SQL执行的后置处理了,在SQL执行之后,执行查询操作获取值或者设置需要返回哪些自增键,设置到入参对象对应属性中

  1. 如果KeyGeneratorJdbc3KeyGenerator类型,也就是配置useGeneratedKeys="true"

    1. 执行写操作,设置需要返回自增键,可通过getGeneratedKeys()方法获取
    2. 获得受影响的行数
    3. 执行后置处理,调用其processAfter方法,也就是将我们配置的自增键设置到入参对象中
  2. 如果KeyGeneratorSelectKeyGenerator类型,也就是添加了<selectKey />标签

    1. 执行写操作
    2. 获得受影响的行数
    3. 执行后置处理,调用其processAfter方法,也就是执行查询操作获取值,设置到入参对象对应属性中
  3. 如果没有配置KeyGenerator

    1. 执行写操作
    2. 获得受影响的行数

PreparedStatementHandler

org.apache.ibatis.executor.statement.PreparedStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.PreparedStatement进行数据库操作(默认),部分代码如下:

public class PreparedStatementHandler extends BaseStatementHandler { public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql); } @Override public void batch(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 添加到批处理 ps.addBatch(); } @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行 ps.execute(); // 结果处理器并返回结果 return resultSetHandler.handleResultSets(ps); } @Override public <E> Cursor<E> queryCursor(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行 ps.execute(); // 结果处理器并返回 Cursor 结果 return resultSetHandler.handleCursorResultSets(ps); } }
  • 上面的方法都很简单,都是直接通过PreparedStatement对象执行数据库操作

  • 在查询方法中,需要通过resultSetHandler对结果集进行映射,返回对应的Java对象

instantiateStatement方法

创建一个PreparedStatement对象,方法如下:

@Override protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); /* * 获得 KeyGenerator 对象 * 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象 * 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象 * 否则为 NoKeyGenerator 对象 */ // <1> 处理 Jdbc3KeyGenerator 的情况 if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { // <1.1> 获得 keyColumn 配置 String[] keyColumnNames = mappedStatement.getKeyColumns(); if (keyColumnNames == null) { // <1.2 >创建 PreparedStatement 对象,并返回自增键,并可通过 getGeneratedKeys() 方法获取 return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { // <1.3> 创建 PreparedStatement 对象,并返回我们配置的 column 列名自增键,并可通过 getGeneratedKeys() 方法获取 return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) { // <2> 创建 PrepareStatement 对象 return connection.prepareStatement(sql); } else { // <3> 创建 PrepareStatement 对象,指定 ResultSetType return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } }
  1. 处理Jdbc3KeyGenerator的情况,也就是配置了useGeneratedKeys="true"

    1. 获得keyColumn配置,哪些自增键需要返回
    2. 如果keyColumn为null,返回PreparedStatement对象,设置RETURN_GENERATED_KEYS,表示所有自增列都返回,可通过getGeneratedKeys()方法获取
    3. 如果keyColumn不为null,返回PreparedStatement对象,设置需要返回的自增列为keyColumn,可通过getGeneratedKeys()方法获取
  2. 没有配置Jdbc3KeyGenerator对象,创建PreparedStatement对象返回,默认情况

  3. 没有配置Jdbc3KeyGenerator对象,但是指定了ResultSetType,则返回PreparedStatement对象,指定ResultSetType

parameterize方法

设置PreparedStatement的占位符参数,方法如下:

@Override public void parameterize(Statement statement) throws SQLException { // 通过 DefaultParameterHandler 设置 PreparedStatement 的占位符参数 parameterHandler.setParameters((PreparedStatement) statement); }

update方法

执行数据库更新操作,方法如下:

@Override public int update(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行 ps.execute(); // 获得更新数量 int rows = ps.getUpdateCount(); // 入参对象 Object parameterObject = boundSql.getParameterObject(); /* * 获得 KeyGenerator 对象 * 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象 * 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象 * 否则为 NoKeyGenerator 对象 */ KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); // 执行 keyGenerator 的后置处理逻辑,也就是对我们配置的自增键进行赋值 keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject); return rows; }
  1. 执行数据库更新操作
  2. 获得受影响行数
  3. 根据配置的KeyGenerator对象,执行后置处理,执行查询操作获取值,或者获取返回的自增键,设置到入参对象对应属性中

CallableStatementHandler

org.apache.ibatis.executor.statement.CallableStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.CallableStatement进行数据库操作,用于存储过程

在执行完数据库的操作后需要调用DefaultResultSetHandlerhandleOutputParameters方法,处理需要作为出参的参数,这里就不做过得的讲述了😈

KeyGenerator

在上面已经讲到在执行数据库更新操作时,需要通过KeyGenerator来进行前置处理或者后置处理,我一般用于自增主键

先来看看它的实现类,如下图所示:

KeyGenerator
  • org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator:实现KeyGenerator接口,只有后置处理的实现,获取返回的自增键,设置到入参的属性中,配置了useGeneratedKeys="true"则会创建该对象

  • org.apache.ibatis.executor.keygen.SelectKeyGenerator:实现KeyGenerator接口,执行数据库查询操作,获取到对应的返回结果设置到入参的属性中,添加了<selectKey />标签则会创建该对象,前后置处理可以配置

  • org.apache.ibatis.executor.keygen.NoKeyGenerator:实现KeyGenerator接口,空实现,一个单例,没有配置上面的两种方式,则默认为该对象

KeyGenerator接口,代码如下:

public interface KeyGenerator { /** * 在 SQL 执行后设置自增键到入参中 * * @param executor 执行器 * @param ms MappedStatement 对象 * @param stmt Statement对象 * @param parameter 入参对象 */ void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter); /** * 在 SQL 执行前设置自增键到入参中 * * @param executor 执行器 * @param ms MappedStatement 对象 * @param stmt Statement对象 * @param parameter 入参对象 */ void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter); }

Jdbc3KeyGenerator

org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator:实现KeyGenerator接口,只有后置处理的实现,获取返回的自增键,设置到入参的属性中,配置了useGeneratedKeys="true"则会创建该对象,在《MyBatis初始化(二)之加载Mapper接口与XML映射文件》XMLStatementBuilder小节的parseStatementNode方法中的第8步看到

实现方法:

public class Jdbc3KeyGenerator implements KeyGenerator { @Override public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { // do nothing } @Override public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { // 批处理多个自增键 processBatch(ms, stmt, parameter); } }

可以看到前置处理的实现方法为空,后置处理的实现方法调用processBatch方法

processBatch方法

processBatch(MappedStatement ms, Statement stmt, Object parameter)方法,从结果集中获自增键设置到入参对象的属性中,代码如下:

public void processBatch(MappedStatement ms, Statement stmt, Object parameter) { // 获取 keyProperty 配置 final String[] keyProperties = ms.getKeyProperties(); if (keyProperties == null || keyProperties.length == 0) { return; } // 获取 Statement 执行后自增键对应的 ResultSet 对象 try (ResultSet rs = stmt.getGeneratedKeys()) { // 获取 ResultSet 的 ResultSetMetaData 元数据对象 final ResultSetMetaData rsmd = rs.getMetaData(); final Configuration configuration = ms.getConfiguration(); if (rsmd.getColumnCount() < keyProperties.length) { // 自增键与 keyProperty 数量不一致则跳过 // Error? } else { assignKeys(configuration, rs, rsmd, keyProperties, parameter); } } catch (Exception e) { throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e); } }
  1. 获取keyProperty配置,也就是需要将自增键设置到入参对象的属性名称
  2. 通过StatementgetGeneratedKeys()方法获取到自增键
  3. 如果自增键与属性个数不相同则跳过,不进行处理了
  4. 否则调用assignKeys方法,分配自增键给对应的属性

assignKeys方法

/** * 关于 ParamMap,可以看到 {@link org.apache.ibatis.reflection.ParamNameResolver#getNamedParams} 这个方法 * 根据入参获取获取参数名称与参数值的映射关系 * 如果为多个入参或者一个入参并且添加了 @Param 注解,则会返回 ParamMap 对象,key为参数名称,value为参数值 * * 需要使用到获取自增键时,我们一般入参都是一个实体类,则进入的是下面第3种情况 */ @SuppressWarnings("unchecked") private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties, Object parameter) throws SQLException { if (parameter instanceof ParamMap || parameter instanceof StrictMap) { // <1> // Multi-param or single param with @Param assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter); } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty() && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) { // <2> // Multi-param or single param with @Param in batch operation assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, ((ArrayList<ParamMap<?>>) parameter)); } else { // <3> // Single param without @Param assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter); } }
  • 因为我的入参通常是一个实体类的时候,配置自增键的生成,这里我们直接看第三种情况,其他两种可参考我的注释进行阅读

assignKeysToParam方法

private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties, Object parameter) throws SQLException { // 将入参对象 parameter 转换成集合,因为批处理时可能传入多个入参对象 Collection<?> params = collectionize(parameter); if (params.isEmpty()) { return; } List<KeyAssigner> assignerList = new ArrayList<>(); for (int i = 0; i < keyProperties.length; i++) { // 每一个 keyProperty 都创建一个 KeyAssigner 对象,设置 column 的位置 assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i])); } Iterator<?> iterator = params.iterator(); while (rs.next()) { if (!iterator.hasNext()) { throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size())); } Object param = iterator.next(); // 往每个入参对象中设置配置的 keyProperty 为对应的自增键 assignerList.forEach(x -> x.assign(rs, param)); } }
  1. 将入参对象parameter转换成集合,因为批处理时可能传入多个入参对象
  2. 每一个keyProperty都创建一个KeyAssigner对象,设置自增键的位置,通过该对象往入参中设置该属性值
  3. 遍历入参params集合,每个入参对象都遍历assignerList集合,通过KeyAssigner往入参中设置属性值

KeyAssigner

定义在Jdbc3KeyGenerator的一个内部类,单个自增键的分配者,将该自增键设置到入参对象的属性中,构造方法如下:

private class KeyAssigner { /** * 全局配置对象 */ private final Configuration configuration; /** * Statement 执行后返回的 元数据对象 */ private final ResultSetMetaData rsmd; /** * 类型处理器注册中心 */ private final TypeHandlerRegistry typeHandlerRegistry; /** * 属性对应列所在的位置 */ private final int columnPosition; /** * 参数名称,添加了 @Param 注解时才有 */ private final String paramName; /** * Java 属性名称 */ private final String propertyName; /** * 类型处理器 */ private TypeHandler<?> typeHandler; protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName, String propertyName) { super(); this.configuration = configuration; this.rsmd = rsmd; this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); this.columnPosition = columnPosition; this.paramName = paramName; this.propertyName = propertyName; } }
  • 定义了结果元数据ResultSetMetaData对象、自增键所在位置、属性名称、类型处理器
assign方法

assign(ResultSet rs, Object param)方法,将当前自增键设置到param入参的属性中,代码如下:

protected void assign(ResultSet rs, Object param) { if (paramName != null) { // If paramName is set, param is ParamMap param = ((ParamMap<?>) param).get(paramName); } MetaObject metaParam = configuration.newMetaObject(param); try { if (typeHandler == null) { if (metaParam.hasSetter(propertyName)) { Class<?> propertyType = metaParam.getSetterType(propertyName); // 根据 Java Type 和 Jdbc Type 获取对应的类型处理器 typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, JdbcType.forCode(rsmd.getColumnType(columnPosition))); } else { throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '" + metaParam.getOriginalObject().getClass().getName() + "'."); } } if (typeHandler == null) { // Error? } else { // 将 Jdbc Type 转换成 Java Type Object value = typeHandler.getResult(rs, columnPosition); // 将该属性值设置到 入参对象中 metaParam.setValue(propertyName, value); } } catch (SQLException e) { throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e); } }
  • 从结果集中根据位置获取到该自增键,然后设置到入参对象的属性中

SelectKeyGenerator

org.apache.ibatis.executor.keygen.SelectKeyGenerator:实现KeyGenerator接口,执行数据库查询操作,获取到对应的返回结果设置到入参的属性中,添加了<selectKey />标签则会创建该对象,前后置处理可以配置,在《MyBatis初始化(二)之加载Mapper接口与XML映射文件》XMLStatementBuilder小节的parseStatementNode方法中的第7步看到

实现方法:

public class SelectKeyGenerator implements KeyGenerator { /** * <selectKey /> 解析成 MappedStatement 对象的 id 的后缀 * 例如 <selectKey /> 所在的 MappedStatement 的 id 为 namespace.test * 那么它的 id 就是 namespace.test!selectKey */ public static final String SELECT_KEY_SUFFIX = "!selectKey"; /** * 是否在 SQL 执行后执行 */ private final boolean executeBefore; /** * <selectKey /> 解析成 MappedStatement 对象 */ private final MappedStatement keyStatement; @Override public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { if (executeBefore) { // 如果是在 SQL 执行之前进行生成 key processGeneratedKeys(executor, ms, parameter); } } @Override public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { if (!executeBefore) { // 如果是在 SQL 执行之后进行生成 key processGeneratedKeys(executor, ms, parameter); } } }
  • 可以看到<selectKey />标签还会生成一个MappedStatement对象,用于执行查询语句

  • executeBefore属性表示,是否为前置处理,所以<selectKey />要么就是前置处理,要么就是后置处理,都是调用processGeneratedKeys方法

processGeneratedKeys方法

执行数据库的查询操作,生成“主键”,设置到入参对象的属性中,代码如下:

private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) { try { if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) { // <1> 获取 keyProperty 配置 String[] keyProperties = keyStatement.getKeyProperties(); final Configuration configuration = ms.getConfiguration(); // <2> 创建入参 MetaObject 对象 final MetaObject metaParam = configuration.newMetaObject(parameter); if (keyProperties != null) { // Do not close keyExecutor. // The transaction will be closed by parent executor. // <3> 创建一个 SimpleExecutor 执行器 Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE); // <4> 执行数据库查询 List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER); if (values.size() == 0) { throw new ExecutorException("SelectKey returned no data."); } else if (values.size() > 1) { throw new ExecutorException("SelectKey returned more than one value."); } else { // <5> 为数据库查询的到数据创建 MetaObject 对象 MetaObject metaResult = configuration.newMetaObject(values.get(0)); if (keyProperties.length == 1) { // <6.1> 单个属性 if (metaResult.hasGetter(keyProperties[0])) { // 往入参中设置该属性为从数据库查询到的数据 setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0])); } else { // no getter for the property - maybe just a single value object // so try that setValue(metaParam, keyProperties[0], values.get(0)); } } else { //<6.2> 多个属性 handleMultipleProperties(keyProperties, metaParam, metaResult); } } } } } catch (ExecutorException e) { throw e; } catch (Exception e) { throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e); } } private void setValue(MetaObject metaParam, String property, Object value) { if (metaParam.hasSetter(property)) { metaParam.setValue(property, value); } else { throw new ExecutorException("省略..."); } }
  1. 获取keyProperty配置,也就是需要设置值到入参中的属性名称
  2. 创建入参的MetaObject对象metaParam,便于操作
  3. 创建一个SimpleExecutor执行器
  4. 使用该执行器执行数据库查询操作
  5. 为数据库查询获取到的结果创建MetaObject对象metaResult
  6. 将查询结果设置到入参对象中
    1. 如果keyProperty配置的仅仅是一个属性,则从metaResult中获取查询结果设置到metaParam入参对象的该属性中
    2. 如果多个属性需要赋值,则调用handleMultipleProperties方法将查询结果设置到入参对象的多个属性中

handleMultipleProperties方法

private void handleMultipleProperties(String[] keyProperties, MetaObject metaParam, MetaObject metaResult) { // 获取 keyColumn 配置 String[] keyColumns = keyStatement.getKeyColumns(); if (keyColumns == null || keyColumns.length == 0) { // 没有配置列名则直接去属性名 // no key columns specified, just use the property names for (String keyProperty : keyProperties) { // 往入参中设置该属性为从数据库查询到的数据,从查询到的结果中取属性名 setValue(metaParam, keyProperty, metaResult.getValue(keyProperty)); } } else { if (keyColumns.length != keyProperties.length) { // 列名和属性名的个数不一致则抛出异常 throw new ExecutorException("If SelectKey has key columns, the number must match the number of key properties."); } for (int i = 0; i < keyProperties.length; i++) { // 往入参中设置该属性为从数据库查询到的数据,从查询到的结果中取列名 setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i])); } } }
  1. 没有配置列名,则根据keyProperty属性名称从metaResult查询结果中获取结果,一个一个设置到metaParam入参对象的该属性中
  2. 没有配置列名,则根据keyColumn列名从metaResult查询结果中获取结果,一个一个设置到metaParam入参对象的该属性中

NoKeyGenerator

org.apache.ibatis.executor.keygen.NoKeyGenerator:实现KeyGenerator接口,空实现,一个单例,没有配置上面的两种方式,则默认为该对象,代码如下:

public class NoKeyGenerator implements KeyGenerator { /** * A shared instance. * @since 3.4.3 */ public static final NoKeyGenerator INSTANCE = new NoKeyGenerator(); @Override public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { // Do Nothing } @Override public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { // Do Nothing } }

总结

本文分析了MyBatis在执行SQL的过程中,SimpleExecutor(默认类型)执行器需要通过PrepareStatementHandler(默认)来执行数据库的操作,创建PrepareStatement(默认)对象来完成数据操作

如果你配置了useGeneratedKeys="true",则需要在执行完数据库更新操作后,通过Jdbc3KeyGenerator设置自增键到入参对象中(后置处理)

如果你添加了<selectKey />标签,则需要通过SelectKeyGenerator执行数据库查询操作获取到结果,设置到入参对象中(前置处理、后置处理)

如果是查询操作则需要通过ResultSetHandler对结果集进行映射转换成Java对象,这就是我们下一篇文档需要分析的内容😄

参考文章:芋道源码《精尽 MyBatis 源码分析》


__EOF__

本文作者月圆
本文链接https://www.cnblogs.com/lifullmoon/p/14015149.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   月圆吖  阅读(927)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示