mybatis源码解读:executor包(主键自增功能)

executor执行器包作为mybatis的核心将其他各个包凝聚在一起,会调用配置解析包解析出配置信息,会依赖基础包提供的基础功能,最终executor包将所有的操作串连在一起,通过session包向外暴露出一套完整的服务。


1.主键自增功能

在进行数据插入操作时,经常需要一个自增生成的主键编号,这既能保证主键的唯一性, 又能保证主键的连续性。mybatis的executor包中的keygen子包提供主键自增功能。

1.主键自增的配置与生效

mybatis通过KeyGenerator接口提供主键自增功能,而KeyGenerator的实现类有Jdbc3KeyGenenrator、SelectKeyGenerator、NoKeyGenerator这3种。在实际使用时,这3种实现类中只能有一种实现类生效,而如果生效的是NoKeyGenerator则表明不具有任何主键自增功能。

在XMLStatementBuilder类中有用到主键功能被解析。

/**
* // 单条语句的解析器,解析类似:
* // <select id="selectUser" resultType="User">
* // select * from `user` where id = #{id}
* // </select>
*/
public class XMLStatementBuilder extends BaseBuilder {
/* 解析select、insert、update、delete这四类节点
*/
public void parseStatementNode() {
// 读取当前节点的id与databaseId
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 验证id与databaseId是否匹配。MyBatis允许多数据库配置,因此有些语句只对特定数据库生效
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

// 读取节点名
String nodeName = context.getNode().getNodeName();
// 读取和判断语句类型
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

// 处理语句中的Include节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 参数类型
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 语句类型
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);

// 处理SelectKey节点,在这里会将KeyGenerator加入到Configuration.keyGenerators中
processSelectKeyNodes(id, parameterTypeClass, langDriver);

// 此时,<selectKey> 和 <include> 节点均已被解析完毕并被删除,开始进行SQL解析
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// 判断是否已经有解析好的KeyGenerator
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 全局或者本语句只要启用自动key生成,则使用key生成
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

// 读取各个配置属性
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 在MapperBuilderAssistant的帮助下创建MappedStatement对象,并写入到Configuration中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}

processSelectKeyNodes方法最终解析了selectKey节点从xml中删除了,而解析出来的信息则放入了configuration的keyGenerators中。之后如果没有解析好的keyGenerator,则会更根据useGeneratedKeys判断是否使用Jdbc3KeyGenerator.

 最终KeyGenerator信息会被保存在整个Statement中。在Statement执行时直接调用KeyGenerator中的processBefore方法和processAfter方法即可,必然会有Jdbc3KeyGenerator、SelectKeyGenerator、NoKeyGenerator三者中的一个来实际执行者两个方法。

2. Jdbc3KeyGenerator类

2.1. Jdbc3KeyGenerator类的功能

数据库已经支持主键自增了,那么Jdbc3KeyGenerator类存在的意义是什么呢?

它存在的意义是提供自增主键的回写功能,能够将数据库中产生的id值回写给java对象本身。

2.2. Jdbc3KeyGenerator类的原理

Jdbc3KeyGenerator类的工作是在数据库主键自增结束后,将自增处理的主键读取处理并赋给java对象。这些工作都是在数据插入后进行的,即在processAfter方法中进行,而processBefore方法中不需要进行任何操作。

processAfter方法直接调用了processBatch方法,而processBatch方法主要工作就是调用Statement对象的getGeneratedKeys方法获取数据库自增生成的主键,然后将主键赋给实参以达到回写的目的。

public class Jdbc3KeyGenerator implements KeyGenerator {
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// 拿到主键的属性名
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
// 没有主键则无需操作
return;
}
// 调用Statement对象的getGeneratedKeys方法获取自动生成的主键值
try (ResultSet rs = stmt.getGeneratedKeys()) {
// 获取输出结果的描述信息
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// 主键数目比结果的总字段数目还多,则发生了错误。
// 但因为此处是获取主键这样的附属操作,因此忽略错误,不影响主要工作
} else {
// 调用子方法,将主键值赋给实参
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
}