14

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

 3 years ago
source link: http://www.cnblogs.com/lifullmoon/p/14015149.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

该系列文档是本人在学习 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的SQL执行过程

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

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

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

ABJJjm2.png!mobile

在 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接口的实现类如下图所示:

rEVfmy.png!mobile
  • 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 会在后续讲到:smiling_imp:

    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 ,这个映射过程非常复杂,会有单独一篇文档进行分析:smiling_imp:

    可以看到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"

    getGeneratedKeys()
    processAfter
    
  2. 如果 KeyGeneratorSelectKeyGenerator 类型,也就是添加了 <selectKey /> 标签

    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 方法,处理需要作为出参的参数,这里就不做过得的讲述了:smiling_imp:

KeyGenerator

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

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

zeeAJrj.png!mobile
  • 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对象,这就是我们下一篇文档需要分析的内容:smile:

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK