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

文章目录

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

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

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

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 {...@Overridepublic <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);}@Overridepublic <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

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

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 {@Overridepublic void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {// Do Nothing}@Overridepublic void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {// Do Nothing}
}

3.4.3 使用JDBC3键值生成器

Jdbc3KeyGeneration.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 {@Overridepublic void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {// Do Nothing}@Overridepublic 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

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;}@Overridepublic void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {if (executeBefore) {processGeneratedKeys(executor, ms, parameter);}}@Overridepublic 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

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#getBoundSqlreturn 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

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

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

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);}@Overridepublic Statement prepare(Connection connection) {Statement statement = null;try {// 实例化 Statementstatement = 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

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);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();return connection.prepareStatement(sql);}@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}@Overridepublic 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;}@Overridepublic <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

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

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.queryUserInfoByIdid = applyCurrentNamespace(id, false);MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);statementBuilder.resource(resource);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);// 结果映射, 给 MappedStatement#resultMapssetStatementResultMap(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 返回结果,所以这里返回的是 nullresultMap = 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 + activityMapid = 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

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);// 外部应用 resultMapString 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/RawSqlSourceSqlSource 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/RawSqlSourceSqlSource 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 赋值。
<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

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

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 herereturn (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

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;}@Overridepublic Connection getConnection() throws SQLException {if (null != connection) {return connection;}connection = dataSource.getConnection();connection.setTransactionIsolation(level.getLevel());connection.setAutoCommit(autoCommit);return connection;}@Overridepublic void commit() throws SQLException {if (connection != null && !connection.getAutoCommit()) {connection.commit();}}@Overridepublic void rollback() throws SQLException {if (connection != null && !connection.getAutoCommit()) {connection.rollback();}}@Overridepublic 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

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 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_timeFROM activityWHERE 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

@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();
}

测试结果

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

@Test
public void test_insert_select() throws IOException {// 解析 XMLReader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);Configuration configuration = xmlConfigBuilder.parse();// 获取 DefaultSqlSessionfinal 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();
}

测试结果

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 必须是在同一个链接下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/121636.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Milvus以及Web UI 安装

向量数据库懂的都懂 版本数据 [rootiZ7xv7q4im4c48qen2do2bZ project]# cat /etc/redhat-release CentOS Stream release 9 [rootiZ7xv7q4im4c48qen2do2bZ project]# docker version Client: Docker Engine - CommunityVersion: 24.0.5API version: 1.43Go v…

qt文件操作

对话框练习 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//字体按钮 void Widget::on_ztbtn_clicked() {//调用QFontDia…

《C++ Primer》第2章 变量(一)

参考资料&#xff1a; 《C Primer》第5版《C Primer 习题集》第5版 2.1 基本内置类型&#xff08;P30&#xff09; C 定义的基本类型包括算术类型&#xff08;arithmetic type&#xff09;和空类型&#xff08;void&#xff09;&#xff0c;其中算术类型包括字符、整型、布尔…

python实现对excel表中的某列数据进行排序

如下需要对webCms中的B列数据进行升序排序&#xff0c;且不能影响到其他列、工作表中的数据和格式。 import pandas as pd import openpyxl from openpyxl.utils.dataframe import dataframe_to_rows# 读取 Excel 文件 file_path 1.xlsx sheet_name webCms# 读取 Excel 文件并…

Pycharm中出现ImportError:DLL load failed:找不到指定模块的解决方法

不论搭建什么工程&#xff0c;运行什么文件&#xff0c;只要在Pycharm中出现ImportError: DLL load failed: 找不到指定的模块这样的问题&#xff0c;以下方法都适用&#xff01;&#xff01;&#xff01; 一、问题描述 我在使用pycharm连接webots&#xff0c;用python控制机…

ToBeWritten之威胁狩猎

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 转移发布平台通知&#xff1a;将不再在CSDN博客发布新文章&#xff0c;敬…

机器人中的数值优化(十一)——高斯牛顿法、LMF方法、Dogleg方法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

三相三线电表和三相四线电表有什么区别

三相三线电表和三相四线电表是两种常见的电能计量仪表&#xff0c;它们在结构、接线方式和使用范围上有所不同。本文将从以下几个方面详细介绍两者之间的区别。 一、结构上的区别 1.三相三线电表&#xff1a;三相三线电表主要由电压线圈、电流线圈、转子、铝盘和外壳等部分组成…

lv3 嵌入式开发-2 linux软件包管理

目录 1 软件包管理 1.1流行的软件包管理机制 1.2软件包的类型 1.3软件包的命名 2 在线软件包管理 2.1APT工作原理 2.2更新软件源 2.3APT相关命令 3 离线软件包管理 1 软件包管理 1.1流行的软件包管理机制 Debian Linux首先提出“软件包”的管理机制---Deb软件包 …

Linux设备驱动程序

一、设备驱动程序简介 图1.1 内核功能的划分 可装载模块 Linux有一个很好的特性:内核提供的特性可在运行时进行扩展。这意味着当系统启动 并运行时&#xff0c;我们可以向内核添加功能( 当然也可以移除功能)。 可在运行时添加到内核中的代码被称为“模块”。Linux内核支持好几…

Linux服务器部署JavaWeb后端项目

适用于&#xff1a;MVVM前后台分离开发、部署、域名配置 前端&#xff1a;Vue 后端&#xff1a;Spring Boot 这篇文章只讲后端部署&#xff0c;前端部署戳这里 目录 Step1&#xff1a;服务器上搭建后端所需环境1、更新服务器软件包2、安装JDK83、安装MySQL4、登录MySQL5、修…

基于FPGA的信号发生器(三角波、方波、正弦波)

目录 DDS实现原理 DDS整体设计框图​ Quartus II 仿真​ modelsim仿真 顶层代码 DDS实现原理 DDS(Direct Digital Frequency Synthesizer)直接数字频率合成器,也可叫DDFS。 DDS是从相位的概念直接合成所需波形的一种频率合成技术。 不仅可以产生不同频率的正弦波,而且可…

Java8实战-总结18

Java8实战-总结18 使用流筛选和切片用谓词筛选筛选各异的元素截短流跳过元素 使用流 流让你从外部迭代转向内部迭代。这样&#xff0c;就用不着写下面这样的代码来显式地管理数据集合的迭代(外部迭代)了&#xff1a; List<Dish> vegetarianDishes new ArrayList<>…

ArcGIS美化科研论文地图(利用在线底图)

1.加载在线底图服务 在ArcGIS Desktop中打开Catalog窗口&#xff0c;在GIS Servers中点击Add ArcGIS Server&#xff0c;之后选项全部默认&#xff0c;仅在URL中输入以下网址https://services.arcgisonline.com/arcgis 之后就可以看到底图了 我们在WorldElevation3D中选择Nat…

16-MyCat

一 Mycat概述 1 什么是Mycat 什么是Mycat Mycat是数据库中间件&#xff0c;所谓数据库中间件是连接Java应用程序和数据库中间的软件。 为什么要用Mycat 遇到问题&#xff1a; Java与数据库的紧耦合高访问量高并发对数据库的压力读写请求数据不一致 2 Mycat与其他中间件区别 目…

QT创建可移动点类

效果如图所示&#xff1a; 创建新类MovablePoint&#xff0c;继承自QWidget. MovablePoint头文件: #ifndef MOVABLEPOINT_H #define MOVABLEPOINT_H#include <QWidget> #include <QPainter> #include <QPaintEvent> #include <QStyleOption> #includ…

TS编译选项

自动监控编译 tsc xxx.ts -w 在一个文件夹下&#xff0c;创建 tsconfig.json 文件&#xff0c;在用命令 tsc 就可以自动编译当前文件夹下的ts文件 tsconfig.json文件配置如下&#xff1a; {/*tsconfig.json 是ts编译器的配置文件&#xff0c;ts编译器可以根据它的信息来对代…

springboot自定义表格(动态合并单元格)

一、需求展示&#xff08;一个订单多个商品&#xff0c;商品数量不限订单行合并&#xff09; 二、技术选型&#xff08;jxls自定义模板&#xff09; <!-- 版本具体看官网Release&#xff0c;这里我们使用 2.13.0 --><dependency><groupId>org.jxls</group…

jQuery 层次选择器

jQuery 层次选择器 &#xff08;0&#xff09;测试前的准备工作 A. 定义测试对象 B. 定义测试对象的 CSS 样式 C. 再定义一些测试用的 button。 &#xff08;1&#xff09;所有后代选择器(A B) 所有后代&#xff0c;包括其直接后代及间接后代。 &#xff08;2&#xff09;直…

javaee spring 测试aop 切面

切面类 package com.test.advice;import org.aspectj.lang.ProceedingJoinPoint;//增强类 public class MyAdvice {//将这个增强方法切入到service层的add方法前public void before(){System.out.println("添加用户之前");}}目标类 package com.test.service;publi…