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

骑猪看日落 2022-12-30 14:50 474阅读 0赞

欢迎关注本人公众号:

20201220181855568.png

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

1.主键自增功能

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

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

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

启用Jdbc3KeyGenerator,可以在配置文件中增加设置

  1. <setting name="useGeneratedKey" value="true" />

或者直接在语句中配置

  1. <insert id="insert" parameterType="User" useGeneratedKey="true" keyProperty="id"></insert>

启用SelectKeyGenerator则需要在sql语句前配置

  1. <insert id="insert" parameterType="User" > <selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER"> select LAST_INSERT_ID() </selectKey></insert>

注意:如果一条语句中同时设置了useGeneratedKey和selectKey ,则selectKey 生效。

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

  1. /** * // 单条语句的解析器,解析类似: * // <select id="selectUser" resultType="User"> * // select * from `user` where id = #{id} * // </select> */public class XMLStatementBuilder extends BaseBuilder {
  2. /* 解析select、insert、update、delete这四类节点 */ public void parseStatementNode() {
  3. // 读取当前节点的id与databaseId String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // 验证id与databaseId是否匹配。MyBatis允许多数据库配置,因此有些语句只对特定数据库生效 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  4. 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)) {
  5. keyGenerator = configuration.getKeyGenerator(keyStatementId); } else {
  6. // 全局或者本语句只要启用自动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) {
  7. 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三者中的一个来实际执行者两个方法。

  1. Jdbc3KeyGenerator类

2.1. Jdbc3KeyGenerator类的功能

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

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

2.2. Jdbc3KeyGenerator类的原理

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

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

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

3.SelectKeyGenerator类

Jdbc3KeyGenerator类其实并没有真正地生成自增主键,而只是把数据库自增出的主键值回写到了java对象中。所以面对不支持主键自增功能的数据库时,Jdbc3KeyGenerator类将无能为力,而SelectKeyGenerator类可以真正地生成自增的主键。

  1. public class SelectKeyGenerator implements KeyGenerator {
  2. // 用户生成主键的SQL语句的特有标志,该标志会追加在用于生成主键的SQL语句的id的后方 public static final String SELECT_KEY_SUFFIX = "!selectKey"; // 插入前执行还是插入后执行 private final boolean executeBefore; // 用户生成主键的SQL语句 private final MappedStatement keyStatement;​ public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
  3. this.executeBefore = executeBefore; this.keyStatement = keyStatement; }​ /** * 数据插入前进行的操作 * @param executor 执行器 * @param ms 映射语句对象 * @param stmt Statement对象 * @param parameter SQL语句实参对象 */ @Override public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  4. if (executeBefore) {
  5. processGeneratedKeys(executor, ms, parameter); } }​ /** * 数据插入后进行的操作 * @param executor 执行器 * @param ms 映射语句对象 * @param stmt Statement对象 * @param parameter SQL语句实参对象 */ @Override public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  6. if (!executeBefore) {
  7. processGeneratedKeys(executor, ms, parameter); } }​ /** * 执行一段SQL语句后获取一个值,然后将该值赋给Java对象的自增属性 * * @param executor 执行器 * @param ms 插入操作的SQL语句(不是生成主键的SQL语句) * @param parameter 插入操作的对象 */ private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
  8. try {
  9. // keyStatement为生成主键的SQL语句;keyStatement.getKeyProperties()拿到的是要自增的属性 if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
  10. // 要自增的属性 String[] keyProperties = keyStatement.getKeyProperties(); final Configuration configuration = ms.getConfiguration(); final MetaObject metaParam = configuration.newMetaObject(parameter); if (keyProperties != null) {
  11. // 为生成主键的SQL语句创建执行器keyExecutor。 // 原注释:不要关闭keyExecutor,因为它会被父级的执行器关闭 Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE); // 执行SQL语句,得到主键值 List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER); // 主键值必须唯一 if (values.size() == 0) {
  12. throw new ExecutorException("SelectKey returned no data."); } else if (values.size() > 1) {
  13. throw new ExecutorException("SelectKey returned more than one value."); } else {
  14. MetaObject metaResult = configuration.newMetaObject(values.get(0)); if (keyProperties.length == 1) {
  15. // 要自增的主键只有一个,为其赋值 if (metaResult.hasGetter(keyProperties[0])) {
  16. // 从metaResult中用getter得到主键值 setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0])); } else {
  17. // 可能返回的直接就是主键值本身 setValue(metaParam, keyProperties[0], values.get(0)); } } else {
  18. // 要把执行SQL得到的值赋给多个属性 handleMultipleProperties(keyProperties, metaParam, metaResult); } } } } } catch (ExecutorException e) {
  19. throw e; } catch (Exception e) {
  20. throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e); } } }

SelectKeyGenerator类的功能就是先执行一段特定的sql语句获取一个值,然后将该值赋给java对象的自增属性。然而这个功能执行时机分为以下2种,这2种执行时机通过配置二选一。

1.在数据库插入之前执行,执行完特定的sql语句并将值赋给java对象的自增属性后,再将这个对象插入数据库中。而这种操作又分2种情况:

1.1 如果数据库没有设置或者不支持主键自增,则完整的对象会被完整地插入数据库中。

1.2 如果数据库设置了主键自增,则刚才特定sql语句生成的自增属性值会被数据库自身的自增值覆盖。这种情况下java对象的自增属性值可能会和数据库中的自增属性值不一致,因此是错误的。这种情况下,建议使用Jdbc3KeyGenerator类的回写功能。

2.在数据库插入后执行,对象插入数据库结束后,java对象的自增属性被设置成特定sql语句的执行结果。这种操作也分2种情况:

2.1 如果数据库不支持主键自增,则之前被插入数据库中的对象的自增属性是没有被赋值的,而java对象的自增属性却被赋值了,这会导致不一致。这种操作是错误的。

2.2 如果数据库设置了主键自增,则数据库自增生成的值和sql语句执行产生的值可能不一样。不过可以通过设置特定的sql语句来保证两者一致,这其实和Jdbc3KeyGenerator类回写功能类似。

可见SelectKeyGenerator类功能简单又灵活,但因为执行时机、数据库状况等不同可能产生多种情况,需要注意。

发表评论

表情:
评论列表 (有 0 条评论,474人围观)

还没有评论,来说两句吧...

相关阅读