文章目录
- 一、目标:Insert自增索引值
- 二、设计:Insert自增索引值
- 三、实现:Insert自增索引值
-
- [3.1 工程结构](#3.1 工程结构)
- [3.2 Insert自增索引值类图](#3.2 Insert自增索引值类图)
- [3.3 修改执行器](#3.3 修改执行器)
-
- [3.3.1 修改执行器接口](#3.3.1 修改执行器接口)
- [3.3.2 抽象执行器基类](#3.3.2 抽象执行器基类)
- [3.4 键值生成器](#3.4 键值生成器)
-
- [3.4.1 键值生成器接口](#3.4.1 键值生成器接口)
- [3.4.2 不用键值生成器](#3.4.2 不用键值生成器)
- [3.4.3 使用JDBC3键值生成器](#3.4.3 使用JDBC3键值生成器)
- [3.4.4 不自增键值生成器](#3.4.4 不自增键值生成器)
- [3.5 配置和映射中添加键值生成器](#3.5 配置和映射中添加键值生成器)
-
- [3.5.1 映射器语句类](#3.5.1 映射器语句类)
- [3.5.2 配置项](#3.5.2 配置项)
- [3.5.3 默认Map结果处理器](#3.5.3 默认Map结果处理器)
- [3.6 解析selectkey](#3.6 解析selectkey)
-
- [3.6.1 修改语句处理器抽象基类](#3.6.1 修改语句处理器抽象基类)
- [3.6.2 预处理语句处理器](#3.6.2 预处理语句处理器)
- [3.6.3 注解配置构建器](#3.6.3 注解配置构建器)
- [3.6.4 映射构建器助手](#3.6.4 映射构建器助手)
- [3.6.5 XML语句构建器](#3.6.5 XML语句构建器)
- [3.7 JDBC类型和类型处理器注册机修改](#3.7 JDBC类型和类型处理器注册机修改)
-
- [3.7.1 JDBC类型](#3.7.1 JDBC类型)
- [3.7.2 类型处理器注册机](#3.7.2 类型处理器注册机)
- [3.8 JDBC链接获取](#3.8 JDBC链接获取)
- 四、测试:Insert自增索引值
-
- [4.1 测试环境配置](#4.1 测试环境配置)
-
- [4.1.1 修改DAO持久层](#4.1.1 修改DAO持久层)
- [4.1.2 修改mapper配置文件](#4.1.2 修改mapper配置文件)
- [4.2 单元测试](#4.2 单元测试)
-
- [4.2.1 单元测试](#4.2.1 单元测试)
- [4.2.2 插入查询测试](#4.2.2 插入查询测试)
- 五、总结:Insert自增索引值
一、目标:Insert自增索引值
💡 在执行插入 SQL 后要返回此条插入语句后的自增索引?
- 当一次数据库操作有2条执行 SQL 语句的时候,重点在于必须在同一个 DB 连接下,否则将会失去事务的特性。
- 也就表示着,如果不是同一 DB 连接下,返回的自增 ID 将会是一个
0
值。
- 也就表示着,如果不是同一 DB 连接下,返回的自增 ID 将会是一个
- 目标 :在解析 Mapper 配置文件以后,调用预处理语句处理器执行 SQL 时,需要在同一个链接下进行处理。
二、设计:Insert自增索引值
💡 在执行插入 SQL 后返回插入的索引值。
- 需要在
insert
标签中新增selectKey
标签,并在selectKey
标签中执行查询数据库索引的操作。 - 所以基于这样的新增标签,则会在 XML 语句构建器中添加对
selectKey
标签的解析,并同样也需要把新增的解析映射器语句存放到配置项中。 - 最终到执行 SQL 时,在一个 DB 连接下,完成两个 SQL 的操作。
- 以解析 Mapper XML 为入口处理
insert/delete/update/select
类型的 SQL 为入口,获取selectKey
标签,并对此标签内的 SQL 进行解析封装。- 把它当成一个查询操作,封装成映射语句。
- 注意 :这里只会对
insert
标签起作用,其他标签并不会配置selectKey
的操作。
- 当把
selectKey
解析完成以后,也是像解析其他类型的标签一样,按照 MappedStatement 映射器语句存放到 Configuration 配置项中。- 这样后面执行 DefaultSqlSession 获取 SQL 的时候就可以从配置项获取,并在执行器完成 SQL 的操作。
- 注意 :对于键值的处理,是单独包装的 KeyGenerator 键值生成器,完成 SQL 的调用和结果封装的。
- 另外由于这里执行了2条 SQL 语句,一条插入,一条查询。而2条 SQL 必须在同一个 DB 连接下,才能把正确的索引值返回回来。
- 因为这样是保证了一个事务的特性,否则是查询不到插入的索引值的。
- 注意 :获取链接是在
JdbcTransaction#getConnection
方法获取的,只有获取的链接是已经存在的同一个才能正确执行返回结果。
三、实现:Insert自增索引值
3.1 工程结构
java
mybatis-step-14
|-src
|-main
| |-java
| |-com.lino.mybatis
| |-annotations
| | |-Delete.java
| | |-Insert.java
| | |-Select.java
| | |-Update.java
| |-binding
| | |-MapperMethod.java
| | |-MapperProxy.java
| | |-MapperProxyFactory.java
| | |-MapperRegistry.java
| |-builder
| | |-annotations
| | | |-MapperAnnotationBuilder.java
| | |-xml
| | | |-XMLConfigBuilder.java
| | | |-XMLMapperBuilder.java
| | | |-XMLStatementBuilder.java
| | |-BaseBuilder.java
| | |-MapperBuilderAssistant.java
| | |-ParameterExpression.java
| | |-ResultMapResolver.java
| | |-SqlSourceBuilder.java
| | |-StaticSqlSource.java
| |-datasource
| | |-druid
| | | |-DruidDataSourceFacroty.java
| | |-pooled
| | | |-PooledConnection.java
| | | |-PooledDataSource.java
| | | |-PooledDataSourceFacroty.java
| | | |-PoolState.java
| | |-unpooled
| | | |-UnpooledDataSource.java
| | | |-UnpooledDataSourceFacroty.java
| | |-DataSourceFactory.java
| |-executor
| | |-keygen
| | | |-Jdbc3KeyGenerator.java
| | | |-KeyGenerator.java
| | | |-NoKeyGenerator.java
| | | |-SelectKeyGenerator.java
| | |-parameter
| | | |-ParameterHandler.java
| | |-result
| | | |-DefaultResultContext.java
| | | |-DefaultResultHandler.java
| | |-resultset
| | | |-DefaultResultSetHandler.java
| | | |-ResultSetHandler.java
| | | |-ResultSetWrapper.java
| | |-statement
| | | |-BaseStatementHandler.java
| | | |-PreparedStatementHandler.java
| | | |-SimpleStatementHandler.java
| | | |-StatementHandler.java
| | |-BaseExecutor.java
| | |-Executor.java
| | |-SimpleExecutor.java
| |-io
| | |-Resources.java
| |-mapping
| | |-BoundSql.java
| | |-Environment.java
| | |-MappedStatement.java
| | |-ParameterMapping.java
| | |-ResultFlag.java
| | |-ResultMap.java
| | |-ResultMapping.java
| | |-SqlCommandType.java
| | |-SqlSource.java
| |-parsing
| | |-GenericTokenParser.java
| | |-TokenHandler.java
| |-reflection
| | |-factory
| | | |-DefaultObjectFactory.java
| | | |-ObjectFactory.java
| | |-invoker
| | | |-GetFieldInvoker.java
| | | |-Invoker.java
| | | |-MethodInvoker.java
| | | |-SetFieldInvoker.java
| | |-property
| | | |-PropertyNamer.java
| | | |-PropertyTokenizer.java
| | |-wrapper
| | | |-BaseWrapper.java
| | | |-BeanWrapper.java
| | | |-CollectionWrapper.java
| | | |-DefaultObjectWrapperFactory.java
| | | |-MapWrapper.java
| | | |-ObjectWrapper.java
| | | |-ObjectWrapperFactory.java
| | |-MetaClass.java
| | |-MetaObject.java
| | |-Reflector.java
| | |-SystemMetaObject.java
| |-scripting
| | |-defaults
| | | |-DefaultParameterHandler.java
| | | |-RawSqlSource.java
| | |-xmltags
| | | |-DynamicContext.java
| | | |-MixedSqlNode.java
| | | |-SqlNode.java
| | | |-StaticTextSqlNode.java
| | | |-XMLLanguageDriver.java
| | | |-XMLScriptBuilder.java
| | |-LanguageDriver.java
| | |-LanguageDriverRegistry.java
| |-session
| | |-defaults
| | | |-DefaultSqlSession.java
| | | |-DefaultSqlSessionFactory.java
| | |-Configuration.java
| | |-ResultContext.java
| | |-ResultHandler.java
| | |-RowBounds.java
| | |-SqlSession.java
| | |-SqlSessionFactory.java
| | |-SqlSessionFactoryBuilder.java
| | |-TransactionIsolationLevel.java
| |-transaction
| | |-jdbc
| | | |-JdbcTransaction.java
| | | |-JdbcTransactionFactory.java
| | |-Transaction.java
| | |-TransactionFactory.java
| |-type
| | |-BaseTypeHandler.java
| | |-DateTypeHandler.java
| | |-IntegerTypeHandler.java
| | |-JdbcType.java
| | |-LongTypeHandler.java
| | |-StringTypeHandler.java
| | |-TypeAliasRegistry.java
| | |-TypeHandler.java
| | |-TypeHandlerRegistry.java
|-test
|-java
| |-com.lino.mybatis.test
| |-dao
| | |-IActivityDao.java
| |-po
| | |-Activity.java
| |-ApiTest.java
|-resources
|-mapper
| |-Activity_Mapper.xml
|-mybatis-config-datasource.xml
3.2 Insert自增索引值类图
- 在整个核心实现类中,以 XMLStatmentBuilder 新增
processSelectKeyNodes
节点解析,构建 MappedStatement 映射类语句。 - 此处的映射类语句会通过 KeyGenerator 键值生成器实现类包装。
- 它的包装主要用于负责对
selectkey
标签中 SQL 语句的查询和结果封装。 - 类似于 DefaultSqlSession 调用 Executor 执行器的过程。
- 它的包装主要用于负责对
- 另外整个
insert
的执行,需要在 PreparedStatementHandler 预处理语句处理器的update
方法进行扩充,执行完插入操作,再执行查询处理。- 方法的返回值返回的是 SQL 影响的条数,入参中的对象属性与
selectKey
配置的一样的名称字段,会被填充索引值返回。 - 注意 :在 Mybatis 框架中,所有的 SQL 操作,在执行器中只有
select
和update
,也就是insert/delete/update
都被update
封装处理了。
- 方法的返回值返回的是 SQL 影响的条数,入参中的对象属性与
3.3 修改执行器
3.3.1 修改执行器接口
Executor.java
java
package com.lino.mybatis.executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;
/**
* @description: 执行器
*/
public interface Executor {
...
/**
* 查询
*
* @param ms 映射器语句
* @param parameter 参数
* @param rowBounds 分页记录限制
* @param resultHandler 结果处理器
* @param boundSql SQL对象
* @param <E> 返回的类型
* @return List<E>
* @throws SQLException SQL异常
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
/**
* 查询
*
* @param ms 映射器语句
* @param parameter 参数
* @param rowBounds 分页记录限制
* @param resultHandler 结果处理器
* @param <E> 返回的类型
* @return List<E>
* @throws SQLException SQL异常
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
...
}
3.3.2 抽象执行器基类
BaseExecutor.java
java
package com.lino.mybatis.executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* @description: 执行器抽象基类
*/
public abstract class BaseExecutor implements Executor {
...
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
return doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return query(ms, parameter, rowBounds, resultHandler, boundSql);
}
/**
* 更新方法
*
* @param ms 映射器语句
* @param parameter 参数
* @return 返回的是受影响的行数
* @throws SQLException SQL异常
*/
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
/**
* 查询方法
*
* @param ms 映射器语句
* @param parameter 参数
* @param rowBounds 分页记录限制
* @param resultHandler 结果处理器
* @param boundSql SQL对象
* @param <E> 返回的类型
* @return List<E>
* @throws SQLException SQL异常
*/
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
...
}
3.4 键值生成器
- 键值生成器 KeyGenerator 接口和对应的实现类,用于包装对 Mapper XML insert 标签中
selectKey
下语句的处理。 - 这个接口有3个实现类。
NoKeyGenerator
:默认空实现不对主键单独处理。Jdbc3keyGenerator
:主要用于数据库的自增主键,比如 MySQL、PostgreSQL。SelectKeyGenerator
:主要用于数据库不支持自增主键的情况,比如 Oracle、DB2
3.4.1 键值生成器接口
KeyGenerator.java
java
package com.lino.mybatis.executor.keygen;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import java.sql.Statement;
/**
* @description: 键值生成器接口
*/
public interface KeyGenerator {
/**
* 针对Sequence主键而言,在执行insert sql前必须指定一个主键值给要插入的记录
* 如Oracle、DB2,KeyGenerator提供了processBefore()方法。
*
* @param executor 执行器
* @param ms 映射器语句类
* @param stmt 语句类
* @param parameter 参数
*/
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
/**
* 针对自增主键主键的表,在插入时不需要主键,而是在插入过程自动获取一个自增的主键
* 比如MySQL、PostgreSQL,KeyGenerator提供了processAfter()方法
*
* @param executor 执行器
* @param ms 映射器语句类
* @param stmt 语句类
* @param parameter 参数
*/
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
- 在键值生成器接口中定义了2个方法:
processBefore、processAfter
processBefore
:针对 Sequence 主键而言,在执行insert sql
前必须指定一个主键值给要插入的记录。- 比如:Oracle、DB2 ,KeyGenerator 提供了
processBefore()
方法。
- 比如:Oracle、DB2 ,KeyGenerator 提供了
processAfter
:针对自增主键的表,在插入时不需要主键,而是在插入过程自动获取一个自增的主键。- 比如:MySQL、PostgreSQL ,KeyGenerator 提供了
processAfter()
方法。
- 比如:MySQL、PostgreSQL ,KeyGenerator 提供了
3.4.2 不用键值生成器
NoKeyGenerator.java
java
package com.lino.mybatis.executor.keygen;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import java.sql.Statement;
/**
* @description: 不用键值生成器
*/
public class NoKeyGenerator implements KeyGenerator {
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// Do Nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// Do Nothing
}
}
3.4.3 使用JDBC3键值生成器
Jdbc3KeyGeneration.java
java
package com.lino.mybatis.executor.keygen;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* @description: 使用 JDBC3 Statement.getGeneratedKeys
*/
public class Jdbc3KeyGenerator implements KeyGenerator {
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// Do Nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
}
public void processBatch(MappedStatement ms, Statement stmt, List<Object> parameters) {
try (ResultSet rs = stmt.getGeneratedKeys()) {
final Configuration configuration = ms.getConfiguration();
final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
final String[] keyProperties = ms.getKeyProperties();
final ResultSetMetaData rsmd = rs.getMetaData();
TypeHandler<?>[] typeHandlers = null;
if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
for (Object parameter : parameters) {
// there should be one row for each statement (also one for each parameter)
if (!rs.next()) {
break;
}
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (typeHandlers == null) {
// 先取得类型处理器
typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties);
}
// 填充键值
populateKeys(rs, metaParam, keyProperties, typeHandlers);
}
}
} catch (Exception e) {
throw new RuntimeException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
private TypeHandler<?>[] getTypeHandlers(TypeHandlerRegistry typeHandlerRegistry, MetaObject metaParam, String[] keyProperties) {
TypeHandler<?>[] typeHandlers = new TypeHandler<?>[keyProperties.length];
for (int i = 0; i < keyProperties.length; i++) {
if (metaParam.hasSetter(keyProperties[i])) {
Class<?> keyPropertyType = metaParam.getSetterType(keyProperties[i]);
TypeHandler<?> th = typeHandlerRegistry.getTypeHandler(keyPropertyType, null);
typeHandlers[i] = th;
}
}
return typeHandlers;
}
private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException {
for (int i = 0; i < keyProperties.length; i++) {
TypeHandler<?> th = typeHandlers[i];
if (th != null) {
Object value = th.getResult(rs, i + 1);
metaParam.setValue(keyProperties[i], value);
}
}
}
}
3.4.4 不自增键值生成器
SelectKeyGeneration.java
java
package com.lino.mybatis.executor.keygen;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.RowBounds;
import java.sql.Statement;
import java.util.List;
/**
* @description: 键值生成器
*/
public class SelectKeyGenerator implements KeyGenerator {
public static final String SELECT_KEY_SUFFIX = "!selectKey";
private boolean executeBefore;
private MappedStatement keyStatement;
public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
this.executeBefore = executeBefore;
this.keyStatement = keyStatement;
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (!executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
try {
if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
String[] keyProperties = keyStatement.getKeyProperties();
final Configuration configuration = ms.getConfiguration();
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (keyProperties != null) {
Executor keyExecutor = configuration.newExecutor(executor.getTransaction());
List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
if (values.size() == 0) {
throw new RuntimeException("SelectKey returned no data.");
} else if (values.size() > 1) {
throw new RuntimeException("SelectKey returned more than one value.");
} else {
MetaObject metaResult = configuration.newMetaObject(values.get(0));
if (keyProperties.length == 1) {
if (metaResult.hasGetter(keyProperties[0])) {
setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
} else {
setValue(metaParam, keyProperties[0], values.get(0));
}
} else {
handleMultipleProperties(keyProperties, metaParam, metaResult);
}
}
}
}
} catch (Exception e) {
throw new RuntimeException("Error selecting key or setting result to parameter object. Cause: " + e);
}
}
private void handleMultipleProperties(String[] keyProperties, MetaObject metaParam, MetaObject metaResult) {
String[] keyColumns = keyStatement.getKeyColumns();
if (keyColumns == null || keyColumns.length == 0) {
for (String keyProperty : keyProperties) {
setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));
}
} else {
if (keyColumns.length != keyProperties.length) {
throw new RuntimeException("If SelectKey has key columns, the number must match the number of key properties.");
}
for (int i = 0; i < keyProperties.length; i++) {
setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));
}
}
}
private void setValue(MetaObject metaParam, String property, Object value) {
if (metaParam.hasSetter(property)) {
metaParam.setValue(property, value);
} else {
throw new RuntimeException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
}
}
}
- SelectKeyGenerator 核心实现主要体现在
processAfter
方法对processGeneratorKeys
的调用处理。- 这个方法的调用过程中,通过从配置项中获取 JDBC 链接和 Executor 执行器。
- 之后使用执行器对传入进来的 MappedStatement 执行处理,也就是对应的
keyStatement
参数。
- 和执行
select
语句一样,在通过执行器keyExecutor.query
获取到结果以后,使用 MetaObject 反射工具类,向对象属性设置查询结果。- 这个封装的结果,就是封装到了入参对象中对应的字段上,比如:用户对象的
id
字段。
- 这个封装的结果,就是封装到了入参对象中对应的字段上,比如:用户对象的
3.5 配置和映射中添加键值生成器
3.5.1 映射器语句类
MappedStatement.java
java
package com.lino.mybatis.mapping;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.Collections;
import java.util.List;
/**
* @description: 映射器语句类
*/
public class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private SqlCommandType sqlCommandType;
private SqlSource sqlSource;
Class<?> resultType;
private LanguageDriver lang;
private List<ResultMap> resultMaps;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
public MappedStatement() {
}
/**
* 获取SQL对象
*
* @param parameterObject 参数
* @return SQL对象
*/
public BoundSql getBoundSql(Object parameterObject) {
// 调用 SqlSource#getBoundSql
return sqlSource.getBoundSql(parameterObject);
}
public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class<?> resultType) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.sqlSource = sqlSource;
mappedStatement.resultType = resultType;
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys()
&& SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
public Builder resource(String resource) {
mappedStatement.resource = resource;
return this;
}
public String id() {
return mappedStatement.id;
}
public Builder resultMaps(List<ResultMap> resultMaps) {
mappedStatement.resultMaps = resultMaps;
return this;
}
public Builder keyGenerator(KeyGenerator keyGenerator) {
mappedStatement.keyGenerator = keyGenerator;
return this;
}
public Builder keyProperty(String keyProperty) {
mappedStatement.keyProperties = delimitedStringToArray(keyProperty);
return this;
}
}
private static String[] delimitedStringToArray(String in) {
if (in == null || in.trim().length() == 0) {
return null;
} else {
return in.split(",");
}
}
public Configuration getConfiguration() {
return configuration;
}
public String getId() {
return id;
}
public SqlCommandType getSqlCommandType() {
return sqlCommandType;
}
public SqlSource getSqlSource() {
return sqlSource;
}
public Class<?> getResultType() {
return resultType;
}
public LanguageDriver getLang() {
return lang;
}
public List<ResultMap> getResultMaps() {
return resultMaps;
}
public String[] getKeyProperties() {
return keyProperties;
}
public KeyGenerator getKeyGenerator() {
return keyGenerator;
}
public String getResource() {
return resource;
}
public String[] getKeyColumns() {
return keyColumns;
}
}
- 添加
keyGenerator
、keyProperties
、keyColumns
键值生成器
3.5.2 配置项
Configuration.java
java
package com.lino.mybatis.session;
import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @description: 配置项
*/
public class Configuration {
/**
* 环境
*/
protected Environment environment;
/**
* 是否使用自动生成键值对
*/
protected boolean useGeneratedKeys = false;
/**
* 映射注册机
*/
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
/**
* 映射的语句,存在Map里
*/
protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);
/**
* 结果映射,存在Map里
*/
protected final Map<String, ResultMap> resultMaps = new HashMap<>(16);
/**
* 键值生成器,存在Map里
*/
protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>(16);
...
public boolean isUseGeneratedKeys() {
return useGeneratedKeys;
}
public void setUseGeneratedKeys(boolean useGeneratedKeys) {
this.useGeneratedKeys = useGeneratedKeys;
}
...
public void addResultMap(ResultMap resultMap) {
resultMaps.put(resultMap.getId(), resultMap);
}
public void addKeyGenerator(String id, KeyGenerator keyGenerator) {
keyGenerators.put(id, keyGenerator);
}
public KeyGenerator getKeyGenerator(String id) {
return keyGenerators.get(id);
}
public boolean hasKeyGenerators(String id) {
return keyGenerators.containsKey(id);
}
}
- 添加
keyGenerators
键值生成器列表
3.5.3 默认Map结果处理器
DefaultResultSetHandler.java
java
package com.lino.mybatis.executor.resultset;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.result.DefaultResultContext;
import com.lino.mybatis.executor.result.DefaultResultHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* @description: 默认Map结果处理器
*/
public class DefaultResultSetHandler implements ResultSetHandler {
...
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType);
if (typeHandlerRegistry.hasTypeHandler(resultType)) {
// 基本类型
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 普通的Bean对象类型
return objectFactory.create(resultType);
}
throw new RuntimeException("Do not know how to create an instance of " + resultType);
}
private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final Class<?> resultType = resultMap.getType();
final String columnName;
if (!resultMap.getResultMappings().isEmpty()) {
final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
final ResultMapping mapping = resultMappingList.get(0);
columnName = prependPrefix(mapping.getColumn(), columnPrefix);
} else {
columnName = rsw.getColumnNames().get(0);
}
final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
return typeHandler.getResult(rsw.getResultSet(), columnName);
}
private String prependPrefix(String columnName, String prefix) {
if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
return columnName;
}
return prefix + columnName;
}
...
}
3.6 解析selectkey
selectKey
标签主要用在 Mapper XML 中的insert
语句里,所以我们主要是对 XMLStatementBuilder XML 语句构建器的解析过程进行扩展。
3.6.1 修改语句处理器抽象基类
BaseStatementHandler.java
java
package com.lino.mybatis.executor.statement;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @description: 语句处理器抽象基类
* @author: lingjian
* @createDate: 2022/11/8 13:53
*/
public abstract class BaseStatementHandler implements StatementHandler {
protected final Configuration configuration;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final Object parameterObject;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final RowBounds rowBounds;
protected BoundSql boundSql;
public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
// 新增判断,因为update不会传入boundSql参数,这里做初始化处理
if (boundSql == null) {
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterObject = parameterObject;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, resultHandler, boundSql);
}
@Override
public Statement prepare(Connection connection) {
Statement statement = null;
try {
// 实例化 Statement
statement = instantiateStatement(connection);
// 参数设置,可以被抽取,提供配置
statement.setQueryTimeout(350);
statement.setFetchSize(10000);
return statement;
} catch (Exception e) {
throw new RuntimeException("Error prepare statement. Cause: " + e, e);
}
}
/**
* 初始化语句
*
* @param connection 连接
* @return 语句
* @throws SQLException SQL异常
*/
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
/**
* 生成键值对
*
* @param parameter 参数
*/
protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
}
}
3.6.2 预处理语句处理器
- StatementHandler 语句处理器接口所定义的方法,在 SQL 执行上只有
update
和query
。 - 所以我们要扩展的
insert
操作,也是对update
方法的扩展操作处理。
PreparedStatementHandler.java
java
package com.lino.mybatis.executor.statement;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* @description: 预处理语句处理器(PREPARED)
*/
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultSetHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameterObject, rowBounds, resultSetHandler, boundSql);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
return connection.prepareStatement(sql);
}
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
}
- 在
update
方法中扩展对selectKey
标签中语句的处理。 - 其实这部分内容就是获取 MapperStatement 映射语句类中 KeyGenerator ,之后调用
KeyGenerator#processAfter
方法。 - 这部分的调用就是前面对
SelectKeyGenerator#processGeneratorKeys
键值生成器方法的调用讲解。
3.6.3 注解配置构建器
MapperAnnotationBuilder.java
java
package com.lino.mybatis.builder.annotation;
import com.lino.mybatis.annotations.Delete;
import com.lino.mybatis.annotations.Insert;
import com.lino.mybatis.annotations.Select;
import com.lino.mybatis.annotations.Update;
import com.lino.mybatis.binding.MapperMethod;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
/**
* @description: 注解配置构建器 Mapper
*/
public class MapperAnnotationBuilder {
private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();
private Configuration configuration;
private MapperBuilderAssistant assistant;
private Class<?> type;
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace(".", "/") + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
assistant.setCurrentNamespace(type.getName());
Method[] methods = type.getMethods();
for (Method method : methods) {
if (!method.isBridge()) {
// 解析语句
parseStatement(method);
}
}
}
}
/**
* 解析语句
*
* @param method 方法
*/
private void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
final String mappedStatementId = type.getName() + "." + method.getName();
SqlCommandType sqlCommandType = getSqlCommandType(method);
KeyGenerator keyGenerator;
String keyProperty = "id";
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
} else {
keyGenerator = new NoKeyGenerator();
}
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
String resultMapId = null;
if (isSelect) {
resultMapId = parseResultMap(method);
}
// 调用助手类
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
sqlCommandType,
parameterTypeClass,
resultMapId,
getReturnType(method),
keyGenerator,
keyProperty,
languageDriver
);
}
}
...
}
3.6.4 映射构建器助手
MapperBuilderAssistant.java
java
package com.lino.mybatis.builder;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.mapping.*;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import java.util.ArrayList;
import java.util.List;
/**
* @description: 映射构建器助手,建造者
*/
public class MapperBuilderAssistant extends BaseBuilder {
private String currentNamespace;
private String resource;
public MapperBuilderAssistant(Configuration configuration, String resource) {
super(configuration);
this.resource = resource;
}
public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, List<ResultFlag> flags) {
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, null);
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, null);
ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
builder.typeHandler(typeHandlerInstance);
builder.flags(flags);
return builder.build();
}
private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
if (javaType == null && property != null) {
try {
MetaClass metaResultType = MetaClass.forClass(resultType);
javaType = metaResultType.getSetterType(property);
} catch (Exception ignore) {
}
}
if (javaType == null) {
javaType = Object.class;
}
return javaType;
}
public String getCurrentNamespace() {
return currentNamespace;
}
public void setCurrentNamespace(String currentNamespace) {
this.currentNamespace = currentNamespace;
}
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
if (isReference) {
if (base.contains(".")) {
return base;
}
} else {
if (base.startsWith(currentNamespace + ".")) {
return base;
}
if (base.contains(".")) {
throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);
}
}
return currentNamespace + "." + base;
}
/**
* 添加映射器语句
*/
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,
Class<?> parameterType, String resultMap, Class<?> resultType,
KeyGenerator keyGenerator, String keyProperty, LanguageDriver lang) {
// 给id加上namespace前缀:com.lino.mybatis.test.dao.IUserDao.queryUserInfoById
id = applyCurrentNamespace(id, false);
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);
statementBuilder.resource(resource);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
// 结果映射, 给 MappedStatement#resultMaps
setStatementResultMap(resultMap, resultType, statementBuilder);
MappedStatement statement = statementBuilder.build();
// 映射语句信息,建造完存放到配置项中
configuration.addMappedStatement(statement);
return statement;
}
private void setStatementResultMap(String resultMap, Class<?> resultType, MappedStatement.Builder statementBuilder) {
// 因为暂时还没有在 Mapper XML 中配置 Map 返回结果,所以这里返回的是 null
resultMap = applyCurrentNamespace(resultMap, true);
List<ResultMap> resultMaps = new ArrayList<>();
if (resultMap != null) {
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
}
}
/*
* 通常使用 resultType 即可满足大部分场景
* <select id="queryUserInfoById" resultType="com.lino.mybatis.test.po.User">
* 使用 resultType 的情况下,Mybatis 会自动创建一个 ResultMap,基于属性名称映射列到 JavaBean 的属性上。
*/
else if (resultType != null) {
ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<>());
resultMaps.add(inlineResultMapBuilder.build());
}
statementBuilder.resultMaps(resultMaps);
}
public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {
// 补全ID全路径,如:com.lino.mybatis.test.dao.IActivityDao + activityMap
id = applyCurrentNamespace(id, false);
ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings);
ResultMap resultMap = inlineResultMapBuilder.build();
configuration.addResultMap(resultMap);
return resultMap;
}
}
3.6.5 XML语句构建器
XMLStatementBuilder.java
java
package com.lino.mybatis.builder.xml;
import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.executor.keygen.SelectKeyGenerator;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.List;
import java.util.Locale;
/**
* @description: XML语言构建器
*/
public class XMLStatementBuilder extends BaseBuilder {
private MapperBuilderAssistant builderAssistant;
private Element element;
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, Element element) {
super(configuration);
this.builderAssistant = builderAssistant;
this.element = element;
}
/**
* 解析语句(select|insert|update|delete)
* <select
* id="selectPerson"
* parameterType="int"
* parameterMap="deprecated"
* resultType="hashmap"
* resultMap="personResultMap"
* flushCache="false"
* useCache="true"
* timeout="10000"
* fetchSize="256"
* statementType="PREPARED"
* resultSetType="FORWARD_ONLY">
* SELECT * FROM PERSON WHERE ID = #{id}
* </select>
*/
public void parseStatementNode() {
String id = element.attributeValue("id");
// 参数类型
String parameterType = element.attributeValue("parameterType");
Class<?> parameterTypeClass = resolveAlias(parameterType);
// 外部应用 resultMap
String resultMap = element.attributeValue("resultMap");
// 结果类型
String resultType = element.attributeValue("resultType");
Class<?> resultTypeClass = resolveAlias(resultType);
// 获取命令类型(select|insert|update|delete)
String nodeName = element.getName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 获取默认语言驱动器
Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
LanguageDriver langDriver = configuration.getLanguageRegistry().getDriver(langClass);
// 解析<selectKey>
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 解析成SqlSource,DynamicSqlSource/RawSqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);
// 属性标记【仅对insert有用】,MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
String keyProperty = element.attributeValue("keyProperty");
KeyGenerator keyGenerator = null;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerators(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() :
new NoKeyGenerator();
}
// 调用助手类
builderAssistant.addMappedStatement(id,
sqlSource,
sqlCommandType,
parameterTypeClass,
resultMap,
resultTypeClass,
keyGenerator,
keyProperty,
langDriver);
}
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
List<Element> selectKeyNodes = element.elements("selectKey");
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver);
}
private void parseSelectKeyNodes(String parentId, List<Element> list, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
for (Element nodeToHandle : list) {
String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, languageDriver);
}
}
/**
* <selectKey keyProperty="id" order="AFTER" resultType="long">
* SELECT LAST_INSERT_ID()
* </selectKey>
*/
private void parseSelectKeyNode(String id, Element nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver) {
String resultType = nodeToHandle.attributeValue("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
boolean executeBefore = "BEFORE".equals(nodeToHandle.attributeValue("order", "AFTER"));
String keyProperty = nodeToHandle.attributeValue("keyProperty");
// 默认
String resultMap = null;
KeyGenerator keyGenerator = new NoKeyGenerator();
// 解析成SqlSource,DynamicSqlSource/RawSqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
// 调用助手类
builderAssistant.addMappedStatement(id,
sqlSource,
sqlCommandType,
parameterTypeClass,
resultMap,
resultTypeClass,
keyGenerator,
keyProperty,
langDriver);
// 给id加上namespace前缀
id = builderAssistant.applyCurrentNamespace(id, false);
// 存放键值生成器配置
MappedStatement keyStatement = configuration.getMappedStatement(id);
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}
}
- 通过
parseStatementNode
解析insert/delete/update/select
标签方法,扩展对selectkey
标签的处理。 processSelectKeyNodes
方法是专门用于处理selectKey
标签下的语句。- 另外是对
keyProperty
的初识操作。- 因为很多时候对 SQL 的解析里面并没有
selectKey
以及获取自增主键结果的返回处理。 - 那么这个时候会采用默认的
keyGenerator
获取处理,通常都会是实例化 NoKeyGenerator 赋值。
- 因为很多时候对 SQL 的解析里面并没有
xml
<selectKey keyProperty="id" order="AFTER" resultType="long">
SELECT LAST_INSERT_ID()
</selectKey>
- 在
processSelectKeyNode
中先进行对selectKey
标签上,resultType
、keyProperty
等属性的解析,之后解析 SQL 封装成SqlSession
,最后阶段是对解析信息的保存处理。- 分别存放成 MappedStatement 映射器语句、SelectKeyGenerator 键值生成器。
3.7 JDBC类型和类型处理器注册机修改
3.7.1 JDBC类型
JdbcType.java
java
package com.lino.mybatis.type;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
/**
* @description: JDBC枚举类型
*/
public enum JdbcType {
// JDBC枚举类型
INTEGER(Types.INTEGER),
BIGINT(Types.BIGINT),
FLOAT(Types.FLOAT),
DOUBLE(Types.DOUBLE),
DECIMAL(Types.DECIMAL),
VARCHAR(Types.VARCHAR),
CHAR(Types.CHAR),
TIMESTAMP(Types.TIMESTAMP);
public final int TYPE_CODE;
private static Map<Integer, JdbcType> codeLookup = new HashMap<>();
static {
for (JdbcType type : JdbcType.values()) {
codeLookup.put(type.TYPE_CODE, type);
}
}
JdbcType(int code) {
this.TYPE_CODE = code;
}
public static JdbcType forCode(int code) {
return codeLookup.get(code);
}
}
- 添加
BIGINT
类型
3.7.2 类型处理器注册机
TypeHandlerRegistry.java
java
package com.lino.mybatis.type;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 类型处理器注册机
*/
public final class TypeHandlerRegistry {
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<>(16);
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLER_MAP = new HashMap<>(16);
public TypeHandlerRegistry() {
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(Date.class, new DateTypeHandler());
}
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
register(javaType, null, typeHandler);
}
public void register(JdbcType jdbcType, TypeHandler<?> typeHandler) {
JDBC_TYPE_HANDLER_MAP.put(jdbcType, typeHandler);
}
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (null != javaType) {
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.computeIfAbsent(javaType, k -> new HashMap<>(16));
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLER_MAP.put(handler.getClass(), handler);
}
@SuppressWarnings("unchecked")
public TypeHandler<?> getTypeHandler(Class<?> type, JdbcType jdbcType) {
return getTypeHandler((Type) type, jdbcType);
}
public boolean hasTypeHandler(Class<?> javaType) {
return hasTypeHandler(javaType, null);
}
public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
}
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
}
// type driver generics here
return (TypeHandler<T>) handler;
}
public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) {
return ALL_TYPE_HANDLER_MAP.get(handlerType);
}
}
- 添加
registry
注册方法。
3.8 JDBC链接获取
- 由于是在同一个操作下,处理两条 SQL,分别是插入和返回索引。
- 那么这两条 SQL 其实要在同一个链接下才能正确的获取到结果,也就是保证了一个事务的特性。
JdbcTransaction.java
java
package com.lino.mybatis.transaction.jdbc;
import com.lino.mybatis.session.TransactionIsolationLevel;
import com.lino.mybatis.transaction.Transaction;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @description: JDBC 事务,直接利用 JDBC 的commit、rollback。依赖于数据源获得的连接管理事务范围
*/
public class JdbcTransaction implements Transaction {
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level = TransactionIsolationLevel.NONE;
protected boolean autoCommit;
public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
this.dataSource = dataSource;
this.level = level;
this.autoCommit = autoCommit;
}
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
if (null != connection) {
return connection;
}
connection = dataSource.getConnection();
connection.setTransactionIsolation(level.getLevel());
connection.setAutoCommit(autoCommit);
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.close();
}
}
}
- 也就是
JdbcTransaction#getConnection
方法。- 在前面,我们实现时只是一个
dataSource.getConnection
获取链接。这样就相当于每次获得的链接都是一个新的连接。 - 那么两条 SQL 的执行分别在各自的 JDBC 连接下,则不会正确的返回插入后的索引值。
- 在前面,我们实现时只是一个
- 所以我们这里进行判断,如果连接不为空,则不在创建新的 JDBC 连接,使用当前连接即可。
四、测试:Insert自增索引值
4.1 测试环境配置
4.1.1 修改DAO持久层
IActivityDao.java
java
package com.lino.mybatis.test.dao;
import com.lino.mybatis.test.po.Activity;
/**
* @description: 活动持久层
*/
public interface IActivityDao {
/**
* 根据活动ID查询活动
*
* @param activityId 活动ID
* @return 活动对象
*/
Activity queryActivityById(Long activityId);
/**
* 新增
*
* @param activity 活动类
* @return Integer
*/
Integer insert(Activity activity);
}
4.1.2 修改mapper配置文件
Activity_Mapper.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IActivityDao">
<resultMap id="activityMap" type="com.lino.mybatis.test.po.Activity">
<id column="id" property="id"/>
<result column="activity_id" property="activityId"/>
<result column="activity_name" property="activityName"/>
<result column="activity_desc" property="activityDesc"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="queryActivityById" parameterType="java.lang.Long" resultMap="activityMap">
SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
WHERE activity_id = #{activityId}
</select>
<insert id="insert" parameterType="com.lino.mybatis.test.po.Activity">
INSERT INTO activity
(activity_id, activity_name, activity_desc, create_time, update_time)
VALUES (#{activityId}, #{activityName}, #{activityDesc}, now(), now())
<selectKey keyProperty="id" order="AFTER" resultType="long">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
</mapper>
- 在
insert
标签下,添加selectKey
标签,并使用SELECT LAST_INSERT_ID()
查询方法返回自增索引值。
4.2 单元测试
4.2.1 单元测试
ApiTest.java
java
@Test
public void test_insert() {
// 1.获取映射器对象
IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
Activity activity = new Activity();
activity.setActivityId(10004L);
activity.setActivityName("测试活动");
activity.setActivityDesc("测试数据插入");
activity.setCreator("xiaolingge");
// 2.测试验证
Integer result = dao.insert(activity);
logger.info("测试结果:count: {} index: {}", result, JSON.toJSONString(activity.getId()));
sqlSession.commit();
}
测试结果
java
14:53:54.803 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:10004
14:53:54.803 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"测试活动"
14:53:54.803 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"测试数据插入"
14:53:54.809 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:count: 1 index: 129
- 通过测试结果
index: 129
可以看到,我们已经可以在插入数据后,返回数据库自增字段的结果。
4.2.2 插入查询测试
ApiTest.java
java
@Test
public void test_insert_select() throws IOException {
// 解析 XML
Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);
Configuration configuration = xmlConfigBuilder.parse();
// 获取 DefaultSqlSession
final Environment environment = configuration.getEnvironment();
TransactionFactory transactionFactory = environment.getTransactionFactory();
Transaction tx = transactionFactory.newTransaction(configuration.getEnvironment().getDataSource(), TransactionIsolationLevel.READ_COMMITTED
, false);
// 创建执行器
final Executor executor = configuration.newExecutor(tx);
SqlSession sqlSession = new DefaultSqlSession(configuration, executor);
// 执行查询,默认是一个集合参数
Activity activity = new Activity();
activity.setActivityId(10005L);
activity.setActivityName("测试活动");
activity.setActivityDesc("测试数据插入");
activity.setCreator("xiaolingge");
int result = sqlSession.insert("com.lino.mybatis.test.dao.IActivityDao.insert", activity);
Object obj = sqlSession.selectOne("com.lino.mybatis.test.dao.IActivityDao.insert!selectKey");
logger.info("测试结果:count: {} index: {}", result, JSON.toJSONString(obj));
sqlSession.commit();
}
测试结果
java
14:57:10.348 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:10005
14:57:10.349 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"测试活动"
14:57:10.349 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"测试数据插入"
14:57:10.364 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.insert!selectKey parameter:null
14:57:10.365 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:count: 1 index: 7
- 从测试结果看,记录正常插入到数据库,并返回
index:7
,测试结果通过
五、总结:Insert自增索引值
- 在原有的 Mapper XML 对各类标签语句的解析中,对 insert 操作进行扩展,添加新的标签
selectKey
并通过这样一个标签的解析、执行、封装处理把最终的插入索引结果返回到入参对象的对应属性字段上。- 那么同时我们所处理的是类似 mysql 这样带有自增索引的数据库,用这样的方式来串联整个流程。
- 注意 :本章节是首次在一个操作中执行2条 SQL 语句,为了能让最后可以查询到自增索引,那么这两天 SQL 必须是在同一个链接下。