手写Mybatis:第15章-返回Insert操作自增索引值

文章目录

  • 一、目标:Insert自增索引值
  • 二、设计:Insert自增索引值
  • 三、实现:Insert自增索引值
    • [3.1 工程结构](#3.1 工程结构)
    • [3.2 Insert自增索引值类图](#3.2 Insert自增索引值类图)
    • [3.3 修改执行器](#3.3 修改执行器)
      • [3.3.1 修改执行器接口](#3.3.1 修改执行器接口)
      • [3.3.2 抽象执行器基类](#3.3.2 抽象执行器基类)
    • [3.4 键值生成器](#3.4 键值生成器)
      • [3.4.1 键值生成器接口](#3.4.1 键值生成器接口)
      • [3.4.2 不用键值生成器](#3.4.2 不用键值生成器)
      • [3.4.3 使用JDBC3键值生成器](#3.4.3 使用JDBC3键值生成器)
      • [3.4.4 不自增键值生成器](#3.4.4 不自增键值生成器)
    • [3.5 配置和映射中添加键值生成器](#3.5 配置和映射中添加键值生成器)
      • [3.5.1 映射器语句类](#3.5.1 映射器语句类)
      • [3.5.2 配置项](#3.5.2 配置项)
      • [3.5.3 默认Map结果处理器](#3.5.3 默认Map结果处理器)
    • [3.6 解析selectkey](#3.6 解析selectkey)
      • [3.6.1 修改语句处理器抽象基类](#3.6.1 修改语句处理器抽象基类)
      • [3.6.2 预处理语句处理器](#3.6.2 预处理语句处理器)
      • [3.6.3 注解配置构建器](#3.6.3 注解配置构建器)
      • [3.6.4 映射构建器助手](#3.6.4 映射构建器助手)
      • [3.6.5 XML语句构建器](#3.6.5 XML语句构建器)
    • [3.7 JDBC类型和类型处理器注册机修改](#3.7 JDBC类型和类型处理器注册机修改)
      • [3.7.1 JDBC类型](#3.7.1 JDBC类型)
      • [3.7.2 类型处理器注册机](#3.7.2 类型处理器注册机)
    • [3.8 JDBC链接获取](#3.8 JDBC链接获取)
  • 四、测试:Insert自增索引值
    • [4.1 测试环境配置](#4.1 测试环境配置)
      • [4.1.1 修改DAO持久层](#4.1.1 修改DAO持久层)
      • [4.1.2 修改mapper配置文件](#4.1.2 修改mapper配置文件)
    • [4.2 单元测试](#4.2 单元测试)
      • [4.2.1 单元测试](#4.2.1 单元测试)
      • [4.2.2 插入查询测试](#4.2.2 插入查询测试)
  • 五、总结:Insert自增索引值

一、目标:Insert自增索引值

💡 在执行插入 SQL 后要返回此条插入语句后的自增索引?

  • 当一次数据库操作有2条执行 SQL 语句的时候,重点在于必须在同一个 DB 连接下,否则将会失去事务的特性。
    • 也就表示着,如果不是同一 DB 连接下,返回的自增 ID 将会是一个 0 值。
  • 目标 :在解析 Mapper 配置文件以后,调用预处理语句处理器执行 SQL 时,需要在同一个链接下进行处理。

二、设计:Insert自增索引值

💡 在执行插入 SQL 后返回插入的索引值。

  • 需要在 insert 标签中新增 selectKey 标签,并在 selectKey 标签中执行查询数据库索引的操作。
  • 所以基于这样的新增标签,则会在 XML 语句构建器中添加对 selectKey 标签的解析,并同样也需要把新增的解析映射器语句存放到配置项中。
  • 最终到执行 SQL 时,在一个 DB 连接下,完成两个 SQL 的操作。
  • 以解析 Mapper XML 为入口处理 insert/delete/update/select 类型的 SQL 为入口,获取 selectKey 标签,并对此标签内的 SQL 进行解析封装。
    • 把它当成一个查询操作,封装成映射语句。
    • 注意 :这里只会对 insert 标签起作用,其他标签并不会配置 selectKey 的操作。
  • 当把 selectKey 解析完成以后,也是像解析其他类型的标签一样,按照 MappedStatement 映射器语句存放到 Configuration 配置项中。
    • 这样后面执行 DefaultSqlSession 获取 SQL 的时候就可以从配置项获取,并在执行器完成 SQL 的操作。
    • 注意 :对于键值的处理,是单独包装的 KeyGenerator 键值生成器,完成 SQL 的调用和结果封装的。
  • 另外由于这里执行了2条 SQL 语句,一条插入,一条查询。而2条 SQL 必须在同一个 DB 连接下,才能把正确的索引值返回回来。
    • 因为这样是保证了一个事务的特性,否则是查询不到插入的索引值的。
    • 注意 :获取链接是在 JdbcTransaction#getConnection 方法获取的,只有获取的链接是已经存在的同一个才能正确执行返回结果。

三、实现:Insert自增索引值

3.1 工程结构

java 复制代码
mybatis-step-14
|-src
	|-main
	|	|-java
	|		|-com.lino.mybatis
    |			|-annotations
    |			|	|-Delete.java
	|			|	|-Insert.java
	|			|	|-Select.java
    |			|	|-Update.java
    |			|-binding
    |			|	|-MapperMethod.java
	|			|	|-MapperProxy.java
	|			|	|-MapperProxyFactory.java
    |			|	|-MapperRegistry.java
    |			|-builder
    |			|	|-annotations
    |			|	|	|-MapperAnnotationBuilder.java
    |			|	|-xml
    |			|	|	|-XMLConfigBuilder.java
    |			|	|	|-XMLMapperBuilder.java
    |			|	|	|-XMLStatementBuilder.java
    |			|	|-BaseBuilder.java
    |			|	|-MapperBuilderAssistant.java
    |			|	|-ParameterExpression.java
    |			|	|-ResultMapResolver.java
    |			|	|-SqlSourceBuilder.java
    |			|	|-StaticSqlSource.java
	|			|-datasource
	|			|	|-druid
	|			|	|	|-DruidDataSourceFacroty.java
	|			|	|-pooled
	|			|	|	|-PooledConnection.java
	|			|	|	|-PooledDataSource.java
	|			|	|	|-PooledDataSourceFacroty.java
	|			|	|	|-PoolState.java
	|			|	|-unpooled
	|			|	|	|-UnpooledDataSource.java
	|			|	|	|-UnpooledDataSourceFacroty.java
	|			|	|-DataSourceFactory.java
	|			|-executor
	|			|	|-keygen
	|			|	|	|-Jdbc3KeyGenerator.java
	|			|	|	|-KeyGenerator.java
	|			|	|	|-NoKeyGenerator.java
	|			|	|	|-SelectKeyGenerator.java
	|			|	|-parameter
	|			|	|	|-ParameterHandler.java
	|			|	|-result
	|			|	|	|-DefaultResultContext.java
	|			|	|	|-DefaultResultHandler.java
	|			|	|-resultset
	|			|	|	|-DefaultResultSetHandler.java
	|			|	|	|-ResultSetHandler.java
	|			|	|	|-ResultSetWrapper.java
	|			|	|-statement
	|			|	|	|-BaseStatementHandler.java
	|			|	|	|-PreparedStatementHandler.java
	|			|	|	|-SimpleStatementHandler.java
	|			|	|	|-StatementHandler.java
	|			|	|-BaseExecutor.java
	|			|	|-Executor.java
	|			|	|-SimpleExecutor.java
    |			|-io
    |			|	|-Resources.java
    |			|-mapping
    |			|	|-BoundSql.java
    |			|	|-Environment.java
    |			|	|-MappedStatement.java
    |			|	|-ParameterMapping.java
    |			|	|-ResultFlag.java
    |			|	|-ResultMap.java
    |			|	|-ResultMapping.java
    |			|	|-SqlCommandType.java
    |			|	|-SqlSource.java
    |			|-parsing
    |			|	|-GenericTokenParser.java
    |			|	|-TokenHandler.java
	|			|-reflection
	|			|	|-factory
	|			|	|	|-DefaultObjectFactory.java
	|			|	|	|-ObjectFactory.java
	|			|	|-invoker
	|			|	|	|-GetFieldInvoker.java
	|			|	|	|-Invoker.java
	|			|	|	|-MethodInvoker.java
	|			|	|	|-SetFieldInvoker.java
	|			|	|-property
	|			|	|	|-PropertyNamer.java
	|			|	|	|-PropertyTokenizer.java
	|			|	|-wrapper
	|			|	|	|-BaseWrapper.java
	|			|	|	|-BeanWrapper.java
	|			|	|	|-CollectionWrapper.java
	|			|	|	|-DefaultObjectWrapperFactory.java
	|			|	|	|-MapWrapper.java
	|			|	|	|-ObjectWrapper.java
	|			|	|	|-ObjectWrapperFactory.java
	|			|	|-MetaClass.java
	|			|	|-MetaObject.java
	|			|	|-Reflector.java
	|			|	|-SystemMetaObject.java
	|			|-scripting
	|			|	|-defaults
	|			|	|	|-DefaultParameterHandler.java
	|			|	|	|-RawSqlSource.java
	|			|	|-xmltags
	|			|	|	|-DynamicContext.java
	|			|	|	|-MixedSqlNode.java
	|			|	|	|-SqlNode.java
	|			|	|	|-StaticTextSqlNode.java
	|			|	|	|-XMLLanguageDriver.java
	|			|	|	|-XMLScriptBuilder.java
	|			|	|-LanguageDriver.java
	|			|	|-LanguageDriverRegistry.java
    |			|-session
    |			|	|-defaults
    |			|	|	|-DefaultSqlSession.java
    |			|	|	|-DefaultSqlSessionFactory.java
    |			|	|-Configuration.java
    |			|	|-ResultContext.java
    |			|	|-ResultHandler.java
    |			|	|-RowBounds.java
    |			|	|-SqlSession.java
    |			|	|-SqlSessionFactory.java
    |			|	|-SqlSessionFactoryBuilder.java
    |			|	|-TransactionIsolationLevel.java
    |			|-transaction
    |			|	|-jdbc
    |			|	|	|-JdbcTransaction.java
    |			|	|	|-JdbcTransactionFactory.java
    |			|	|-Transaction.java
    |			|	|-TransactionFactory.java
    |			|-type
    |			|	|-BaseTypeHandler.java
    |			|	|-DateTypeHandler.java
    |			|	|-IntegerTypeHandler.java
    |			|	|-JdbcType.java
    |			|	|-LongTypeHandler.java
    |			|	|-StringTypeHandler.java
    |			|	|-TypeAliasRegistry.java
    |			|	|-TypeHandler.java
    |			|	|-TypeHandlerRegistry.java
	|-test
		|-java
		|	|-com.lino.mybatis.test
		|	|-dao
		|	|	|-IActivityDao.java
		|	|-po
		|	|	|-Activity.java
		|	|-ApiTest.java
        |-resources
			|-mapper
			|	|-Activity_Mapper.xml
        	|-mybatis-config-datasource.xml

3.2 Insert自增索引值类图

  • 在整个核心实现类中,以 XMLStatmentBuilder 新增 processSelectKeyNodes 节点解析,构建 MappedStatement 映射类语句。
  • 此处的映射类语句会通过 KeyGenerator 键值生成器实现类包装。
    • 它的包装主要用于负责对 selectkey 标签中 SQL 语句的查询和结果封装。
    • 类似于 DefaultSqlSession 调用 Executor 执行器的过程。
  • 另外整个 insert 的执行,需要在 PreparedStatementHandler 预处理语句处理器的 update 方法进行扩充,执行完插入操作,再执行查询处理。
    • 方法的返回值返回的是 SQL 影响的条数,入参中的对象属性与 selectKey 配置的一样的名称字段,会被填充索引值返回。
    • 注意 :在 Mybatis 框架中,所有的 SQL 操作,在执行器中只有 selectupdate,也就是 insert/delete/update 都被 update 封装处理了。

3.3 修改执行器

3.3.1 修改执行器接口

Executor.java

java 复制代码
package com.lino.mybatis.executor;

import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;

/**
 * @description: 执行器
 */
public interface Executor {

    ...

    /**
     * 查询
     *
     * @param ms            映射器语句
     * @param parameter     参数
     * @param rowBounds     分页记录限制
     * @param resultHandler 结果处理器
     * @param boundSql      SQL对象
     * @param <E>           返回的类型
     * @return List<E>
     * @throws SQLException SQL异常
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

    /**
     * 查询
     *
     * @param ms            映射器语句
     * @param parameter     参数
     * @param rowBounds     分页记录限制
     * @param resultHandler 结果处理器
     * @param <E>           返回的类型
     * @return List<E>
     * @throws SQLException SQL异常
     */
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

    ...
}

3.3.2 抽象执行器基类

BaseExecutor.java

java 复制代码
package com.lino.mybatis.executor;

import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * @description: 执行器抽象基类
 */
public abstract class BaseExecutor implements Executor {

    ...

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (closed) {
            throw new RuntimeException("Executor was closed.");
        }
        return doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return query(ms, parameter, rowBounds, resultHandler, boundSql);
    }

    /**
     * 更新方法
     *
     * @param ms        映射器语句
     * @param parameter 参数
     * @return 返回的是受影响的行数
     * @throws SQLException SQL异常
     */
    protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

    /**
     * 查询方法
     *
     * @param ms            映射器语句
     * @param parameter     参数
     * @param rowBounds     分页记录限制
     * @param resultHandler 结果处理器
     * @param boundSql      SQL对象
     * @param <E>           返回的类型
     * @return List<E>
     * @throws SQLException SQL异常
     */
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

    ...
}

3.4 键值生成器

  • 键值生成器 KeyGenerator 接口和对应的实现类,用于包装对 Mapper XML insert 标签中 selectKey 下语句的处理。
  • 这个接口有3个实现类。
    • NoKeyGenerator:默认空实现不对主键单独处理。
    • Jdbc3keyGenerator:主要用于数据库的自增主键,比如 MySQL、PostgreSQL
    • SelectKeyGenerator:主要用于数据库不支持自增主键的情况,比如 Oracle、DB2

3.4.1 键值生成器接口

KeyGenerator.java

java 复制代码
package com.lino.mybatis.executor.keygen;

import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import java.sql.Statement;

/**
 * @description: 键值生成器接口
 */
public interface KeyGenerator {

    /**
     * 针对Sequence主键而言,在执行insert sql前必须指定一个主键值给要插入的记录
     * 如Oracle、DB2,KeyGenerator提供了processBefore()方法。
     *
     * @param executor  执行器
     * @param ms        映射器语句类
     * @param stmt      语句类
     * @param parameter 参数
     */
    void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

    /**
     * 针对自增主键主键的表,在插入时不需要主键,而是在插入过程自动获取一个自增的主键
     * 比如MySQL、PostgreSQL,KeyGenerator提供了processAfter()方法
     *
     * @param executor  执行器
     * @param ms        映射器语句类
     * @param stmt      语句类
     * @param parameter 参数
     */
    void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
  • 在键值生成器接口中定义了2个方法:processBefore、processAfter
    • processBefore:针对 Sequence 主键而言,在执行 insert sql 前必须指定一个主键值给要插入的记录。
      • 比如:Oracle、DB2KeyGenerator 提供了 processBefore() 方法。
    • processAfter:针对自增主键的表,在插入时不需要主键,而是在插入过程自动获取一个自增的主键。
      • 比如:MySQL、PostgreSQLKeyGenerator 提供了 processAfter() 方法。

3.4.2 不用键值生成器

NoKeyGenerator.java

java 复制代码
package com.lino.mybatis.executor.keygen;

import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import java.sql.Statement;

/**
 * @description: 不用键值生成器
 */
public class NoKeyGenerator 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) {
        // Do Nothing
    }
}

3.4.3 使用JDBC3键值生成器

Jdbc3KeyGeneration.java

java 复制代码
package com.lino.mybatis.executor.keygen;

import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * @description: 使用 JDBC3 Statement.getGeneratedKeys
 */
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) {

    }

    public void processBatch(MappedStatement ms, Statement stmt, List<Object> parameters) {
        try (ResultSet rs = stmt.getGeneratedKeys()) {
            final Configuration configuration = ms.getConfiguration();
            final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            final String[] keyProperties = ms.getKeyProperties();
            final ResultSetMetaData rsmd = rs.getMetaData();
            TypeHandler<?>[] typeHandlers = null;
            if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
                for (Object parameter : parameters) {
                    // there should be one row for each statement (also one for each parameter)
                    if (!rs.next()) {
                        break;
                    }
                    final MetaObject metaParam = configuration.newMetaObject(parameter);
                    if (typeHandlers == null) {
                        // 先取得类型处理器
                        typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties);
                    }
                    // 填充键值
                    populateKeys(rs, metaParam, keyProperties, typeHandlers);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
        }
    }

    private TypeHandler<?>[] getTypeHandlers(TypeHandlerRegistry typeHandlerRegistry, MetaObject metaParam, String[] keyProperties) {
        TypeHandler<?>[] typeHandlers = new TypeHandler<?>[keyProperties.length];
        for (int i = 0; i < keyProperties.length; i++) {
            if (metaParam.hasSetter(keyProperties[i])) {
                Class<?> keyPropertyType = metaParam.getSetterType(keyProperties[i]);
                TypeHandler<?> th = typeHandlerRegistry.getTypeHandler(keyPropertyType, null);
                typeHandlers[i] = th;
            }
        }
        return typeHandlers;
    }

    private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException {
        for (int i = 0; i < keyProperties.length; i++) {
            TypeHandler<?> th = typeHandlers[i];
            if (th != null) {
                Object value = th.getResult(rs, i + 1);
                metaParam.setValue(keyProperties[i], value);
            }
        }
    }
}

3.4.4 不自增键值生成器

SelectKeyGeneration.java

java 复制代码
package com.lino.mybatis.executor.keygen;

import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.RowBounds;
import java.sql.Statement;
import java.util.List;

/**
 * @description: 键值生成器
 */
public class SelectKeyGenerator implements KeyGenerator {

    public static final String SELECT_KEY_SUFFIX = "!selectKey";
    private boolean executeBefore;
    private MappedStatement keyStatement;

    public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
        this.executeBefore = executeBefore;
        this.keyStatement = keyStatement;
    }

    @Override
    public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
        if (executeBefore) {
            processGeneratedKeys(executor, ms, parameter);
        }
    }

    @Override
    public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
        if (!executeBefore) {
            processGeneratedKeys(executor, ms, parameter);
        }
    }

    private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
        try {
            if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
                String[] keyProperties = keyStatement.getKeyProperties();
                final Configuration configuration = ms.getConfiguration();
                final MetaObject metaParam = configuration.newMetaObject(parameter);
                if (keyProperties != null) {
                    Executor keyExecutor = configuration.newExecutor(executor.getTransaction());
                    List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
                    if (values.size() == 0) {
                        throw new RuntimeException("SelectKey returned no data.");
                    } else if (values.size() > 1) {
                        throw new RuntimeException("SelectKey returned more than one value.");
                    } else {
                        MetaObject metaResult = configuration.newMetaObject(values.get(0));
                        if (keyProperties.length == 1) {
                            if (metaResult.hasGetter(keyProperties[0])) {
                                setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
                            } else {
                                setValue(metaParam, keyProperties[0], values.get(0));
                            }
                        } else {
                            handleMultipleProperties(keyProperties, metaParam, metaResult);
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Error selecting key or setting result to parameter object. Cause: " + e);
        }
    }

    private void handleMultipleProperties(String[] keyProperties, MetaObject metaParam, MetaObject metaResult) {
        String[] keyColumns = keyStatement.getKeyColumns();

        if (keyColumns == null || keyColumns.length == 0) {
            for (String keyProperty : keyProperties) {
                setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));
            }
        } else {
            if (keyColumns.length != keyProperties.length) {
                throw new RuntimeException("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]));
            }
        }
    }

    private void setValue(MetaObject metaParam, String property, Object value) {
        if (metaParam.hasSetter(property)) {
            metaParam.setValue(property, value);
        } else {
            throw new RuntimeException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
        }
    }
}
  • SelectKeyGenerator 核心实现主要体现在 processAfter 方法对 processGeneratorKeys 的调用处理。
    • 这个方法的调用过程中,通过从配置项中获取 JDBC 链接和 Executor 执行器。
    • 之后使用执行器对传入进来的 MappedStatement 执行处理,也就是对应的 keyStatement 参数。
  • 和执行 select 语句一样,在通过执行器 keyExecutor.query 获取到结果以后,使用 MetaObject 反射工具类,向对象属性设置查询结果。
    • 这个封装的结果,就是封装到了入参对象中对应的字段上,比如:用户对象的 id 字段。

3.5 配置和映射中添加键值生成器

3.5.1 映射器语句类

MappedStatement.java

java 复制代码
package com.lino.mybatis.mapping;

import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.Collections;
import java.util.List;

/**
 * @description: 映射器语句类
 */
public class MappedStatement {

    private String resource;
    private Configuration configuration;
    private String id;
    private SqlCommandType sqlCommandType;
    private SqlSource sqlSource;
    Class<?> resultType;
    private LanguageDriver lang;
    private List<ResultMap> resultMaps;

    private KeyGenerator keyGenerator;
    private String[] keyProperties;
    private String[] keyColumns;

    public MappedStatement() {
    }

    /**
     * 获取SQL对象
     *
     * @param parameterObject 参数
     * @return SQL对象
     */
    public BoundSql getBoundSql(Object parameterObject) {
        // 调用 SqlSource#getBoundSql
        return sqlSource.getBoundSql(parameterObject);
    }

    public static class Builder {

        private MappedStatement mappedStatement = new MappedStatement();

        public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class<?> resultType) {
            mappedStatement.configuration = configuration;
            mappedStatement.id = id;
            mappedStatement.sqlCommandType = sqlCommandType;
            mappedStatement.sqlSource = sqlSource;
            mappedStatement.resultType = resultType;
            mappedStatement.keyGenerator = configuration.isUseGeneratedKeys()
                    && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
        }

        public MappedStatement build() {
            assert mappedStatement.configuration != null;
            assert mappedStatement.id != null;
            mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
            return mappedStatement;
        }

        public Builder resource(String resource) {
            mappedStatement.resource = resource;
            return this;
        }

        public String id() {
            return mappedStatement.id;
        }

        public Builder resultMaps(List<ResultMap> resultMaps) {
            mappedStatement.resultMaps = resultMaps;
            return this;
        }

        public Builder keyGenerator(KeyGenerator keyGenerator) {
            mappedStatement.keyGenerator = keyGenerator;
            return this;
        }

        public Builder keyProperty(String keyProperty) {
            mappedStatement.keyProperties = delimitedStringToArray(keyProperty);
            return this;
        }

    }

    private static String[] delimitedStringToArray(String in) {
        if (in == null || in.trim().length() == 0) {
            return null;
        } else {
            return in.split(",");
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getId() {
        return id;
    }

    public SqlCommandType getSqlCommandType() {
        return sqlCommandType;
    }

    public SqlSource getSqlSource() {
        return sqlSource;
    }

    public Class<?> getResultType() {
        return resultType;
    }

    public LanguageDriver getLang() {
        return lang;
    }

    public List<ResultMap> getResultMaps() {
        return resultMaps;
    }

    public String[] getKeyProperties() {
        return keyProperties;
    }

    public KeyGenerator getKeyGenerator() {
        return keyGenerator;
    }

    public String getResource() {
        return resource;
    }

    public String[] getKeyColumns() {
        return keyColumns;
    }
}
  • 添加 keyGeneratorkeyPropertieskeyColumns 键值生成器

3.5.2 配置项

Configuration.java

java 复制代码
package com.lino.mybatis.session;

import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @description: 配置项
 */
public class Configuration {

    /**
     * 环境
     */
    protected Environment environment;
    /**
     * 是否使用自动生成键值对
     */
    protected boolean useGeneratedKeys = false;
    /**
     * 映射注册机
     */
    protected MapperRegistry mapperRegistry = new MapperRegistry(this);
    /**
     * 映射的语句,存在Map里
     */
    protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);
    /**
     * 结果映射,存在Map里
     */
    protected final Map<String, ResultMap> resultMaps = new HashMap<>(16);
    /**
     * 键值生成器,存在Map里
     */
    protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>(16);

    ...

    public boolean isUseGeneratedKeys() {
        return useGeneratedKeys;
    }

    public void setUseGeneratedKeys(boolean useGeneratedKeys) {
        this.useGeneratedKeys = useGeneratedKeys;
    }

    ...

    public void addResultMap(ResultMap resultMap) {
        resultMaps.put(resultMap.getId(), resultMap);
    }

    public void addKeyGenerator(String id, KeyGenerator keyGenerator) {
        keyGenerators.put(id, keyGenerator);
    }

    public KeyGenerator getKeyGenerator(String id) {
        return keyGenerators.get(id);
    }

    public boolean hasKeyGenerators(String id) {
        return keyGenerators.containsKey(id);
    }
}
  • 添加 keyGenerators 键值生成器列表

3.5.3 默认Map结果处理器

DefaultResultSetHandler.java

java 复制代码
package com.lino.mybatis.executor.resultset;

import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.result.DefaultResultContext;
import com.lino.mybatis.executor.result.DefaultResultHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * @description: 默认Map结果处理器
 */
public class DefaultResultSetHandler implements ResultSetHandler {

    ...

    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException {
        final Class<?> resultType = resultMap.getType();
        final MetaClass metaType = MetaClass.forClass(resultType);
        if (typeHandlerRegistry.hasTypeHandler(resultType)) {
            // 基本类型
            return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
        } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
            // 普通的Bean对象类型
            return objectFactory.create(resultType);
        }
        throw new RuntimeException("Do not know how to create an instance of " + resultType);
    }

    private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
        final Class<?> resultType = resultMap.getType();
        final String columnName;
        if (!resultMap.getResultMappings().isEmpty()) {
            final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
            final ResultMapping mapping = resultMappingList.get(0);
            columnName = prependPrefix(mapping.getColumn(), columnPrefix);
        } else {
            columnName = rsw.getColumnNames().get(0);
        }
        final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
        return typeHandler.getResult(rsw.getResultSet(), columnName);
    }

    private String prependPrefix(String columnName, String prefix) {
        if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
            return columnName;
        }
        return prefix + columnName;
    }

    ...
}

3.6 解析selectkey

  • selectKey 标签主要用在 Mapper XML 中的 insert 语句里,所以我们主要是对 XMLStatementBuilder XML 语句构建器的解析过程进行扩展。

3.6.1 修改语句处理器抽象基类

BaseStatementHandler.java

java 复制代码
package com.lino.mybatis.executor.statement;

import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * @description: 语句处理器抽象基类
 * @author: lingjian
 * @createDate: 2022/11/8 13:53
 */
public abstract class BaseStatementHandler implements StatementHandler {

    protected final Configuration configuration;
    protected final Executor executor;
    protected final MappedStatement mappedStatement;

    protected final Object parameterObject;
    protected final ResultSetHandler resultSetHandler;
    protected final ParameterHandler parameterHandler;

    protected final RowBounds rowBounds;
    protected BoundSql boundSql;

    public 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;

        // 新增判断,因为update不会传入boundSql参数,这里做初始化处理
        if (boundSql == null) {
            generateKeys(parameterObject);
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }

        this.boundSql = boundSql;
        this.parameterObject = parameterObject;
        this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, resultHandler, boundSql);
    }

    @Override
    public Statement prepare(Connection connection) {
        Statement statement = null;
        try {
            // 实例化 Statement
            statement = instantiateStatement(connection);
            // 参数设置,可以被抽取,提供配置
            statement.setQueryTimeout(350);
            statement.setFetchSize(10000);
            return statement;
        } catch (Exception e) {
            throw new RuntimeException("Error prepare statement. Cause: " + e, e);
        }
    }

    /**
     * 初始化语句
     *
     * @param connection 连接
     * @return 语句
     * @throws SQLException SQL异常
     */
    protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

   /**
     * 生成键值对
     *
     * @param parameter 参数
     */
    protected void generateKeys(Object parameter) {
        KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
        keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    }
}

3.6.2 预处理语句处理器

  • StatementHandler 语句处理器接口所定义的方法,在 SQL 执行上只有 updatequery
  • 所以我们要扩展的 insert 操作,也是对 update 方法的扩展操作处理。

PreparedStatementHandler.java

java 复制代码
package com.lino.mybatis.executor.statement;

import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * @description: 预处理语句处理器(PREPARED)
 */
public class PreparedStatementHandler extends BaseStatementHandler {

    public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
                                    RowBounds rowBounds, ResultHandler resultSetHandler, BoundSql boundSql) {
        super(executor, mappedStatement, parameterObject, rowBounds, resultSetHandler, boundSql);
    }

    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        return connection.prepareStatement(sql);
    }

    @Override
    public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
    }

    @Override
    public int update(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        int rows = ps.getUpdateCount();
        Object parameterObject = boundSql.getParameterObject();
        KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
        keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
        return rows;
    }

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.handleResultSets(ps);
    }
}
  • update 方法中扩展对 selectKey 标签中语句的处理。
  • 其实这部分内容就是获取 MapperStatement 映射语句类中 KeyGenerator ,之后调用 KeyGenerator#processAfter 方法。
  • 这部分的调用就是前面对 SelectKeyGenerator#processGeneratorKeys 键值生成器方法的调用讲解。

3.6.3 注解配置构建器

MapperAnnotationBuilder.java

java 复制代码
package com.lino.mybatis.builder.annotation;

import com.lino.mybatis.annotations.Delete;
import com.lino.mybatis.annotations.Insert;
import com.lino.mybatis.annotations.Select;
import com.lino.mybatis.annotations.Update;
import com.lino.mybatis.binding.MapperMethod;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

/**
 * @description: 注解配置构建器 Mapper
 */
public class MapperAnnotationBuilder {

    private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();

    private Configuration configuration;
    private MapperBuilderAssistant assistant;
    private Class<?> type;

    public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
        String resource = type.getName().replace(".", "/") + ".java (best guess)";
        this.assistant = new MapperBuilderAssistant(configuration, resource);
        this.configuration = configuration;
        this.type = type;

        sqlAnnotationTypes.add(Select.class);
        sqlAnnotationTypes.add(Insert.class);
        sqlAnnotationTypes.add(Update.class);
        sqlAnnotationTypes.add(Delete.class);
    }

    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            assistant.setCurrentNamespace(type.getName());

            Method[] methods = type.getMethods();
            for (Method method : methods) {
                if (!method.isBridge()) {
                    // 解析语句
                    parseStatement(method);
                }
            }
        }
    }

    /**
     * 解析语句
     *
     * @param method 方法
     */
    private void parseStatement(Method method) {
        Class<?> parameterTypeClass = getParameterType(method);
        LanguageDriver languageDriver = getLanguageDriver(method);
        SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

        if (sqlSource != null) {
            final String mappedStatementId = type.getName() + "." + method.getName();
            SqlCommandType sqlCommandType = getSqlCommandType(method);

            KeyGenerator keyGenerator;
            String keyProperty = "id";
            if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
                keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            } else {
                keyGenerator = new NoKeyGenerator();
            }

            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

            String resultMapId = null;
            if (isSelect) {
                resultMapId = parseResultMap(method);
            }

            // 调用助手类
            assistant.addMappedStatement(
                    mappedStatementId,
                    sqlSource,
                    sqlCommandType,
                    parameterTypeClass,
                    resultMapId,
                    getReturnType(method),
                    keyGenerator,
                    keyProperty,
                    languageDriver
            );
        }
    }

    ...

}

3.6.4 映射构建器助手

MapperBuilderAssistant.java

java 复制代码
package com.lino.mybatis.builder;

import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.mapping.*;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: 映射构建器助手,建造者
 */
public class MapperBuilderAssistant extends BaseBuilder {

    private String currentNamespace;
    private String resource;

    public MapperBuilderAssistant(Configuration configuration, String resource) {
        super(configuration);
        this.resource = resource;
    }

    public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, List<ResultFlag> flags) {
        Class<?> javaTypeClass = resolveResultJavaType(resultType, property, null);
        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, null);

        ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
        builder.typeHandler(typeHandlerInstance);
        builder.flags(flags);

        return builder.build();
    }

    private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
        if (javaType == null && property != null) {
            try {
                MetaClass metaResultType = MetaClass.forClass(resultType);
                javaType = metaResultType.getSetterType(property);
            } catch (Exception ignore) {

            }
        }
        if (javaType == null) {
            javaType = Object.class;
        }
        return javaType;
    }

    public String getCurrentNamespace() {
        return currentNamespace;
    }

    public void setCurrentNamespace(String currentNamespace) {
        this.currentNamespace = currentNamespace;
    }

    public String applyCurrentNamespace(String base, boolean isReference) {
        if (base == null) {
            return null;
        }
        if (isReference) {
            if (base.contains(".")) {
                return base;
            }
        } else {
            if (base.startsWith(currentNamespace + ".")) {
                return base;
            }
            if (base.contains(".")) {
                throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);
            }
        }
        return currentNamespace + "." + base;
    }

    /**
     * 添加映射器语句
     */
    public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,
                                              Class<?> parameterType, String resultMap, Class<?> resultType,
                                              KeyGenerator keyGenerator, String keyProperty, LanguageDriver lang) {
        // 给id加上namespace前缀:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById
        id = applyCurrentNamespace(id, false);
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);
        statementBuilder.resource(resource);
        statementBuilder.keyGenerator(keyGenerator);
        statementBuilder.keyProperty(keyProperty);

        // 结果映射, 给 MappedStatement#resultMaps
        setStatementResultMap(resultMap, resultType, statementBuilder);

        MappedStatement statement = statementBuilder.build();
        // 映射语句信息,建造完存放到配置项中
        configuration.addMappedStatement(statement);

        return statement;
    }

    private void setStatementResultMap(String resultMap, Class<?> resultType, MappedStatement.Builder statementBuilder) {
        // 因为暂时还没有在 Mapper XML 中配置 Map 返回结果,所以这里返回的是 null
        resultMap = applyCurrentNamespace(resultMap, true);

        List<ResultMap> resultMaps = new ArrayList<>();

        if (resultMap != null) {
            String[] resultMapNames = resultMap.split(",");
            for (String resultMapName : resultMapNames) {
                resultMaps.add(configuration.getResultMap(resultMapName.trim()));
            }
        }
        /*
         * 通常使用 resultType 即可满足大部分场景
         * <select id="queryUserInfoById" resultType="com.lino.mybatis.test.po.User">
         * 使用 resultType 的情况下,Mybatis 会自动创建一个 ResultMap,基于属性名称映射列到 JavaBean 的属性上。
         */
        else if (resultType != null) {
            ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
                    configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<>());
            resultMaps.add(inlineResultMapBuilder.build());
        }
        statementBuilder.resultMaps(resultMaps);
    }

    public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {
        // 补全ID全路径,如:com.lino.mybatis.test.dao.IActivityDao + activityMap
        id = applyCurrentNamespace(id, false);

        ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings);

        ResultMap resultMap = inlineResultMapBuilder.build();
        configuration.addResultMap(resultMap);
        return resultMap;
    }
}

3.6.5 XML语句构建器

XMLStatementBuilder.java

java 复制代码
package com.lino.mybatis.builder.xml;

import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.executor.keygen.SelectKeyGenerator;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.List;
import java.util.Locale;

/**
 * @description: XML语言构建器
 */
public class XMLStatementBuilder extends BaseBuilder {

    private MapperBuilderAssistant builderAssistant;
    private Element element;

    public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, Element element) {
        super(configuration);
        this.builderAssistant = builderAssistant;
        this.element = element;
    }

    /**
     * 解析语句(select|insert|update|delete)
     * <select
     * id="selectPerson"
     * parameterType="int"
     * parameterMap="deprecated"
     * resultType="hashmap"
     * resultMap="personResultMap"
     * flushCache="false"
     * useCache="true"
     * timeout="10000"
     * fetchSize="256"
     * statementType="PREPARED"
     * resultSetType="FORWARD_ONLY">
     * SELECT * FROM PERSON WHERE ID = #{id}
     * </select>
     */
    public void parseStatementNode() {
        String id = element.attributeValue("id");
        // 参数类型
        String parameterType = element.attributeValue("parameterType");
        Class<?> parameterTypeClass = resolveAlias(parameterType);
        // 外部应用 resultMap
        String resultMap = element.attributeValue("resultMap");
        // 结果类型
        String resultType = element.attributeValue("resultType");
        Class<?> resultTypeClass = resolveAlias(resultType);
        // 获取命令类型(select|insert|update|delete)
        String nodeName = element.getName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

        // 获取默认语言驱动器
        Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
        LanguageDriver langDriver = configuration.getLanguageRegistry().getDriver(langClass);

        // 解析<selectKey>
        processSelectKeyNodes(id, parameterTypeClass, langDriver);

        // 解析成SqlSource,DynamicSqlSource/RawSqlSource
        SqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);

        // 属性标记【仅对insert有用】,MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
        String keyProperty = element.attributeValue("keyProperty");

        KeyGenerator keyGenerator = null;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);

        if (configuration.hasKeyGenerators(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() :
                    new NoKeyGenerator();
        }

        // 调用助手类
        builderAssistant.addMappedStatement(id,
                sqlSource,
                sqlCommandType,
                parameterTypeClass,
                resultMap,
                resultTypeClass,
                keyGenerator,
                keyProperty,
                langDriver);
    }

    private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
        List<Element> selectKeyNodes = element.elements("selectKey");
        parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver);
    }

    private void parseSelectKeyNodes(String parentId, List<Element> list, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
        for (Element nodeToHandle : list) {
            String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
            parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, languageDriver);
        }
    }

    /**
     * <selectKey keyProperty="id" order="AFTER" resultType="long">
     * SELECT LAST_INSERT_ID()
     * </selectKey>
     */
    private void parseSelectKeyNode(String id, Element nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver) {
        String resultType = nodeToHandle.attributeValue("resultType");
        Class<?> resultTypeClass = resolveClass(resultType);
        boolean executeBefore = "BEFORE".equals(nodeToHandle.attributeValue("order", "AFTER"));
        String keyProperty = nodeToHandle.attributeValue("keyProperty");

        // 默认
        String resultMap = null;
        KeyGenerator keyGenerator = new NoKeyGenerator();

        // 解析成SqlSource,DynamicSqlSource/RawSqlSource
        SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
        SqlCommandType sqlCommandType = SqlCommandType.SELECT;

        // 调用助手类
        builderAssistant.addMappedStatement(id,
                sqlSource,
                sqlCommandType,
                parameterTypeClass,
                resultMap,
                resultTypeClass,
                keyGenerator,
                keyProperty,
                langDriver);

        // 给id加上namespace前缀
        id = builderAssistant.applyCurrentNamespace(id, false);

        // 存放键值生成器配置
        MappedStatement keyStatement = configuration.getMappedStatement(id);
        configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
    }
}
  • 通过 parseStatementNode 解析 insert/delete/update/select 标签方法,扩展对 selectkey 标签的处理。
  • processSelectKeyNodes 方法是专门用于处理 selectKey 标签下的语句。
  • 另外是对 keyProperty 的初识操作。
    • 因为很多时候对 SQL 的解析里面并没有 selectKey 以及获取自增主键结果的返回处理。
    • 那么这个时候会采用默认的 keyGenerator 获取处理,通常都会是实例化 NoKeyGenerator 赋值。
xml 复制代码
<selectKey keyProperty="id" order="AFTER" resultType="long">
    SELECT LAST_INSERT_ID()
</selectKey>
  • processSelectKeyNode 中先进行对 selectKey 标签上,resultTypekeyProperty 等属性的解析,之后解析 SQL 封装成 SqlSession,最后阶段是对解析信息的保存处理。
    • 分别存放成 MappedStatement 映射器语句、SelectKeyGenerator 键值生成器。

3.7 JDBC类型和类型处理器注册机修改

3.7.1 JDBC类型

JdbcType.java

java 复制代码
package com.lino.mybatis.type;

import java.sql.Types;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: JDBC枚举类型
 */
public enum JdbcType {

    // JDBC枚举类型
    INTEGER(Types.INTEGER),
    BIGINT(Types.BIGINT),
    FLOAT(Types.FLOAT),
    DOUBLE(Types.DOUBLE),
    DECIMAL(Types.DECIMAL),
    VARCHAR(Types.VARCHAR),
    CHAR(Types.CHAR),
    TIMESTAMP(Types.TIMESTAMP);

    public final int TYPE_CODE;
    private static Map<Integer, JdbcType> codeLookup = new HashMap<>();

    static {
        for (JdbcType type : JdbcType.values()) {
            codeLookup.put(type.TYPE_CODE, type);
        }
    }

    JdbcType(int code) {
        this.TYPE_CODE = code;
    }

    public static JdbcType forCode(int code) {
        return codeLookup.get(code);
    }
}
  • 添加 BIGINT 类型

3.7.2 类型处理器注册机

TypeHandlerRegistry.java

java 复制代码
package com.lino.mybatis.type;

import java.lang.reflect.Type;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 类型处理器注册机
 */
public final class TypeHandlerRegistry {

    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<>(16);
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLER_MAP = new HashMap<>(16);

    public TypeHandlerRegistry() {
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());

        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());

        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());

        register(Date.class, new DateTypeHandler());
    }

    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        register(javaType, null, typeHandler);
    }

    public void register(JdbcType jdbcType, TypeHandler<?> typeHandler) {
        JDBC_TYPE_HANDLER_MAP.put(jdbcType, typeHandler);
    }

    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (null != javaType) {
            Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.computeIfAbsent(javaType, k -> new HashMap<>(16));
            map.put(jdbcType, handler);
        }
        ALL_TYPE_HANDLER_MAP.put(handler.getClass(), handler);
    }

    @SuppressWarnings("unchecked")
    public TypeHandler<?> getTypeHandler(Class<?> type, JdbcType jdbcType) {
        return getTypeHandler((Type) type, jdbcType);
    }

    public boolean hasTypeHandler(Class<?> javaType) {
        return hasTypeHandler(javaType, null);
    }

    public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
        return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
    }

    private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
        TypeHandler<?> handler = null;
        if (jdbcHandlerMap != null) {
            handler = jdbcHandlerMap.get(jdbcType);
            if (handler == null) {
                handler = jdbcHandlerMap.get(null);
            }
        }
        // type driver generics here
        return (TypeHandler<T>) handler;
    }

    public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) {
        return ALL_TYPE_HANDLER_MAP.get(handlerType);
    }
}
  • 添加 registry 注册方法。

3.8 JDBC链接获取

  • 由于是在同一个操作下,处理两条 SQL,分别是插入和返回索引。
  • 那么这两条 SQL 其实要在同一个链接下才能正确的获取到结果,也就是保证了一个事务的特性。

JdbcTransaction.java

java 复制代码
package com.lino.mybatis.transaction.jdbc;

import com.lino.mybatis.session.TransactionIsolationLevel;
import com.lino.mybatis.transaction.Transaction;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @description: JDBC 事务,直接利用 JDBC 的commit、rollback。依赖于数据源获得的连接管理事务范围
 */
public class JdbcTransaction implements Transaction {

    protected Connection connection;
    protected DataSource dataSource;
    protected TransactionIsolationLevel level = TransactionIsolationLevel.NONE;
    protected boolean autoCommit;

    public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        this.dataSource = dataSource;
        this.level = level;
        this.autoCommit = autoCommit;
    }

    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (null != connection) {
            return connection;
        }
        connection = dataSource.getConnection();
        connection.setTransactionIsolation(level.getLevel());
        connection.setAutoCommit(autoCommit);
        return connection;
    }

    @Override
    public void commit() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.commit();
        }
    }

    @Override
    public void rollback() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.rollback();
        }
    }

    @Override
    public void close() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.close();
        }
    }
}
  • 也就是 JdbcTransaction#getConnection 方法。
    • 在前面,我们实现时只是一个 dataSource.getConnection 获取链接。这样就相当于每次获得的链接都是一个新的连接。
    • 那么两条 SQL 的执行分别在各自的 JDBC 连接下,则不会正确的返回插入后的索引值。
  • 所以我们这里进行判断,如果连接不为空,则不在创建新的 JDBC 连接,使用当前连接即可。

四、测试:Insert自增索引值

4.1 测试环境配置

4.1.1 修改DAO持久层

IActivityDao.java

java 复制代码
package com.lino.mybatis.test.dao;

import com.lino.mybatis.test.po.Activity;

/**
 * @description: 活动持久层
 */
public interface IActivityDao {

    /**
     * 根据活动ID查询活动
     *
     * @param activityId 活动ID
     * @return 活动对象
     */
    Activity queryActivityById(Long activityId);

    /**
     * 新增
     *
     * @param activity 活动类
     * @return Integer
     */
    Integer insert(Activity activity);
}

4.1.2 修改mapper配置文件

Activity_Mapper.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IActivityDao">
    <resultMap id="activityMap" type="com.lino.mybatis.test.po.Activity">
        <id column="id" property="id"/>
        <result column="activity_id" property="activityId"/>
        <result column="activity_name" property="activityName"/>
        <result column="activity_desc" property="activityDesc"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>

    <select id="queryActivityById" parameterType="java.lang.Long" resultMap="activityMap">
        SELECT activity_id, activity_name, activity_desc, create_time, update_time
        FROM activity
        WHERE activity_id = #{activityId}
    </select>

    <insert id="insert" parameterType="com.lino.mybatis.test.po.Activity">
        INSERT INTO activity
        (activity_id, activity_name, activity_desc, create_time, update_time)
        VALUES (#{activityId}, #{activityName}, #{activityDesc}, now(), now())

        <selectKey keyProperty="id" order="AFTER" resultType="long">
            SELECT LAST_INSERT_ID()
        </selectKey>
    </insert>
</mapper>
  • insert 标签下,添加 selectKey 标签,并使用 SELECT LAST_INSERT_ID() 查询方法返回自增索引值。

4.2 单元测试

4.2.1 单元测试

ApiTest.java

java 复制代码
@Test
public void test_insert() {
    // 1.获取映射器对象
    IActivityDao dao = sqlSession.getMapper(IActivityDao.class);

    Activity activity = new Activity();
    activity.setActivityId(10004L);
    activity.setActivityName("测试活动");
    activity.setActivityDesc("测试数据插入");
    activity.setCreator("xiaolingge");

    // 2.测试验证
    Integer result = dao.insert(activity);
    logger.info("测试结果:count: {} index: {}", result, JSON.toJSONString(activity.getId()));

    sqlSession.commit();
}

测试结果

java 复制代码
14:53:54.803 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:10004
14:53:54.803 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"测试活动"
14:53:54.803 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"测试数据插入"
14:53:54.809 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:count: 1 index: 129
  • 通过测试结果 index: 129 可以看到,我们已经可以在插入数据后,返回数据库自增字段的结果。

4.2.2 插入查询测试

ApiTest.java

java 复制代码
@Test
public void test_insert_select() throws IOException {
    // 解析 XML
    Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");
    XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);
    Configuration configuration = xmlConfigBuilder.parse();

    // 获取 DefaultSqlSession
    final Environment environment = configuration.getEnvironment();
    TransactionFactory transactionFactory = environment.getTransactionFactory();
    Transaction tx = transactionFactory.newTransaction(configuration.getEnvironment().getDataSource(), TransactionIsolationLevel.READ_COMMITTED
                                                       , false);

    // 创建执行器
    final Executor executor = configuration.newExecutor(tx);
    SqlSession sqlSession = new DefaultSqlSession(configuration, executor);

    // 执行查询,默认是一个集合参数
    Activity activity = new Activity();
    activity.setActivityId(10005L);
    activity.setActivityName("测试活动");
    activity.setActivityDesc("测试数据插入");
    activity.setCreator("xiaolingge");
    int result = sqlSession.insert("com.lino.mybatis.test.dao.IActivityDao.insert", activity);

    Object obj = sqlSession.selectOne("com.lino.mybatis.test.dao.IActivityDao.insert!selectKey");
    logger.info("测试结果:count: {} index: {}", result, JSON.toJSONString(obj));

    sqlSession.commit();
}

测试结果

java 复制代码
14:57:10.348 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:10005
14:57:10.349 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"测试活动"
14:57:10.349 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"测试数据插入"
14:57:10.364 [main] INFO  c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.insert!selectKey parameter:null
14:57:10.365 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:count: 1 index: 7
  • 从测试结果看,记录正常插入到数据库,并返回 index:7,测试结果通过

五、总结:Insert自增索引值

  • 在原有的 Mapper XML 对各类标签语句的解析中,对 insert 操作进行扩展,添加新的标签 selectKey 并通过这样一个标签的解析、执行、封装处理把最终的插入索引结果返回到入参对象的对应属性字段上。
    • 那么同时我们所处理的是类似 mysql 这样带有自增索引的数据库,用这样的方式来串联整个流程。
  • 注意 :本章节是首次在一个操作中执行2条 SQL 语句,为了能让最后可以查询到自增索引,那么这两天 SQL 必须是在同一个链接下。
相关推荐
陈大爷(有低保)22 分钟前
UDP Socket聊天室(Java)
java·网络协议·udp
阿华的代码王国33 分钟前
MySQL ------- 索引(B树B+树)
数据库·mysql
kinlon.liu35 分钟前
零信任安全架构--持续验证
java·安全·安全架构·mfa·持续验证
王哲晓1 小时前
Linux通过yum安装Docker
java·linux·docker
Hello.Reader1 小时前
StarRocks实时分析数据库的基础与应用
大数据·数据库
java6666688881 小时前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存1 小时前
源码分析:LinkedList
java·开发语言
执键行天涯1 小时前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
Jarlen1 小时前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar