龙岗建网站,wordpress必做,网站建设就业方向,黑龙江建设人力资源网站文章目录 一、目标#xff1a;Insert自增索引值二、设计#xff1a;Insert自增索引值三、实现#xff1a;Insert自增索引值3.1 工程结构3.2 Insert自增索引值类图3.3 修改执行器3.3.1 修改执行器接口3.3.2 抽象执行器基类 3.4 键值生成器3.4.1 键值生成器接口3.4.2 不用键值… 文章目录 一、目标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 解析selectkey3.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.xml3.2 Insert自增索引值类图 在整个核心实现类中以 XMLStatmentBuilder 新增 processSelectKeyNodes 节点解析构建 MappedStatement 映射类语句。此处的映射类语句会通过 KeyGenerator 键值生成器实现类包装。 它的包装主要用于负责对 selectkey 标签中 SQL 语句的查询和结果封装。类似于 DefaultSqlSession 调用 Executor 执行器的过程。 另外整个 insert 的执行需要在 PreparedStatementHandler 预处理语句处理器的 update 方法进行扩充执行完插入操作再执行查询处理。 方法的返回值返回的是 SQL 影响的条数入参中的对象属性与 selectKey 配置的一样的名称字段会被填充索引值返回。注意在 Mybatis 框架中所有的 SQL 操作在执行器中只有 select 和 update也就是 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 ListE* throws SQLException SQL异常*/E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;/*** 查询** param ms 映射器语句* param parameter 参数* param rowBounds 分页记录限制* param resultHandler 结果处理器* param E 返回的类型* return ListE* throws SQLException SQL异常*/E ListE 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 ListE 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 ListE 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 ListE* throws SQLException SQL异常*/protected abstract E ListE 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、DB2KeyGenerator提供了processBefore()方法。** param executor 执行器* param ms 映射器语句类* param stmt 语句类* param parameter 参数*/void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);/*** 针对自增主键主键的表在插入时不需要主键而是在插入过程自动获取一个自增的主键* 比如MySQL、PostgreSQLKeyGenerator提供了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, ListObject 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());ListObject 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 ListResultMap 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(ListResultMap 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 ListResultMap getResultMaps() {return resultMaps;}public String[] getKeyProperties() {return keyProperties;}public KeyGenerator getKeyGenerator() {return keyGenerator;}public String getResource() {return resource;}public String[] getKeyColumns() {return keyColumns;}
}添加 keyGenerator、keyProperties、keyColumns 键值生成器
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 MapString, MappedStatement mappedStatements new HashMap(16);/*** 结果映射存在Map里*/protected final MapString, ResultMap resultMaps new HashMap(16);/*** 键值生成器存在Map里*/protected final MapString, 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, ListClass? constructorArgTypes, ListObject 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 ListResultMapping 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 执行上只有 update 和 query。所以我们要扩展的 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 ListE 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 SetClass? 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, ListResultFlag 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);ListResultMap resultMaps new ArrayList();if (resultMap ! null) {String[] resultMapNames resultMap.split(,);for (String resultMapName : resultMapNames) {resultMaps.add(configuration.getResultMap(resultMapName.trim()));}}/** 通常使用 resultType 即可满足大部分场景* select idqueryUserInfoById resultTypecom.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, ListResultMapping 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* idselectPerson* parameterTypeint* parameterMapdeprecated* resultTypehashmap* resultMappersonResultMap* flushCachefalse* useCachetrue* timeout10000* fetchSize256* statementTypePREPARED* resultSetTypeFORWARD_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);// 解析selectKeyprocessSelectKeyNodes(id, parameterTypeClass, langDriver);// 解析成SqlSourceDynamicSqlSource/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) {ListElement selectKeyNodes element.elements(selectKey);parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver);}private void parseSelectKeyNodes(String parentId, ListElement list, Class? parameterTypeClass, LanguageDriver languageDriver) {for (Element nodeToHandle : list) {String id parentId SelectKeyGenerator.SELECT_KEY_SUFFIX;parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, languageDriver);}}/*** selectKey keyPropertyid orderAFTER resultTypelong* 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();// 解析成SqlSourceDynamicSqlSource/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 keyPropertyid orderAFTER resultTypelongSELECT LAST_INSERT_ID()
/selectKey在 processSelectKeyNode 中先进行对 selectKey 标签上resultType、keyProperty 等属性的解析之后解析 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 MapInteger, 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 MapJdbcType, TypeHandler? JDBC_TYPE_HANDLER_MAP new EnumMap(JdbcType.class);private final MapType, MapJdbcType, TypeHandler? TYPE_HANDLER_MAP new HashMap(16);private final MapClass?, 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) {MapJdbcType, 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 TypeHandlerT getTypeHandler(Type type, JdbcType jdbcType) {MapJdbcType, 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 (TypeHandlerT) 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 version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.lino.mybatis.test.dao.IActivityDaoresultMap idactivityMap typecom.lino.mybatis.test.po.Activityid columnid propertyid/result columnactivity_id propertyactivityId/result columnactivity_name propertyactivityName/result columnactivity_desc propertyactivityDesc/result columncreate_time propertycreateTime/result columnupdate_time propertyupdateTime//resultMapselect idqueryActivityById parameterTypejava.lang.Long resultMapactivityMapSELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activityWHERE activity_id #{activityId}/selectinsert idinsert parameterTypecom.lino.mybatis.test.po.ActivityINSERT INTO activity(activity_id, activity_name, activity_desc, create_time, update_time)VALUES (#{activityId}, #{activityName}, #{activityDesc}, now(), now())selectKey keyPropertyid orderAFTER resultTypelongSELECT 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设置对应的参数信息 value10004
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设置对应的参数信息 value10005
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 - 执行查询 statementcom.lino.mybatis.test.dao.IActivityDao.insert!selectKey parameternull
14:57:10.365 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果count: 1 index: 7从测试结果看记录正常插入到数据库并返回 index7测试结果通过
五、总结Insert自增索引值
在原有的 Mapper XML 对各类标签语句的解析中对 insert 操作进行扩展添加新的标签 selectKey 并通过这样一个标签的解析、执行、封装处理把最终的插入索引结果返回到入参对象的对应属性字段上。 那么同时我们所处理的是类似 mysql 这样带有自增索引的数据库用这样的方式来串联整个流程。 注意本章节是首次在一个操作中执行2条 SQL 语句为了能让最后可以查询到自增索引那么这两天 SQL 必须是在同一个链接下。