美文网首页
MyBatis印象阅读之KeyGenerator解析

MyBatis印象阅读之KeyGenerator解析

作者: 向光奔跑_ | 来源:发表于2019-08-13 10:45 被阅读0次

    在上一章内容中我们又欠下了这些技术债:

    parameterHandler
    resultSetHandler
    KeyGenerator

    今天我们就来着重解决一下关于KeyGenerator的源码。

    1. KeyGenerator解析

    首先我们需要了解这个类主要功能。我们来看官网介绍:
    selectKey 元素中的语句将会首先或之后运行,然后插入语句会被调用。这可以提供给你一个与数据库中自动生成主键类似的行为,同时保持了 Java 代码的简洁。
    我们再来看下有哪些属性:


    selectKey 元素的属性

    介绍完之后我们再来看下源码,先来看接口源码,其中的方法对应order属性的顺序:

    public interface KeyGenerator {
    
      void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
    
      void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
    
    }
    
    他子类继承顺序: KeyGennerator子类继承

    我们以一个例子来进行说明:

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert({"insert into country (countryname,countrycode) values (#{countryname},#{countrycode})"})
    int insertBean(Country country);
    

    然后我们来看一处源码,看用的是KeyGenerator的哪个子类。在MappedStatement的Builder内部类中:

          mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    
    

    我们可以看到用的是Jdbc3KeyGenerator这个类。

    2. Jdbc3KeyGenerator解析

    首先我们先来看类构造方法:

    public class Jdbc3KeyGenerator implements KeyGenerator {
    
      /**
       * A shared instance.
       *
       * @since 3.4.3
       */
      public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
    

    这里可以看到我们用了单例模式中的饿汉式。再来看他的实现方法:

      @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) {
        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;
        }
        try (ResultSet rs = stmt.getGeneratedKeys()) {
          // 获得返回的自增主键
          final ResultSetMetaData rsmd = rs.getMetaData();
          final Configuration configuration = ms.getConfiguration();
          if (rsmd.getColumnCount() < keyProperties.length) {
            // Error?
          } 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);
        }
      }
    

    那么重点来了:

    
      private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
          Object parameter) throws SQLException {
        if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
          // Multi-param or single param with @Param
          assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
        } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
            && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
          // Multi-param or single param with @Param in batch operation
          assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, ((ArrayList<ParamMap<?>>) parameter));
        } else {
          // Single param without @Param
          assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
        }
      }
    

    我们按照顺序来进行分析,首先便是多@Param参数情况下,会形成一个ParamMap,之后调用 assignKeysToParamMap方法:

      private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
          String[] keyProperties, Map<String, ?> paramMap) throws SQLException {
        //如果参数为空直接返回
        if (paramMap.isEmpty()) {
          return;
        }
        Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>();
        for (int i = 0; i < keyProperties.length; i++) {
          //获取组成形成KeyAssigner
          Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i],
              keyProperties, true);
          Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = assignerMap.computeIfAbsent(entry.getKey(),
              k -> entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>()));
          iteratorPair.getValue().add(entry.getValue());
        }
        long counter = 0;
        while (rs.next()) {
          for (Entry<Iterator<?>, List<KeyAssigner>> pair : assignerMap.values()) {
            if (!pair.getKey().hasNext()) {
              throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
            }
            Object param = pair.getKey().next();
            //反射赋值
            pair.getValue().forEach(x -> x.assign(rs, param));
          }
          counter++;
        }
      }
    
    

    大概逻辑就是整理完之后挨个遍历赋值,这里重点便是getAssignerForParamMap方法,我们进入一起看下:

    
     private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd,
          int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) {
        //判断paramMap是否只有一个唯一key
        boolean singleParam = paramMap.values().stream().distinct().count() == 1;
        //获取keyProperty的.的位置
        int firstDot = keyProperty.indexOf('.');
        //如果是多个参数,但是keyProperty又不包含.,会抛出错误
        if (firstDot == -1) {
          if (singleParam) {
            return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
          }
          throw new ExecutorException("Could not determine which parameter to assign generated keys to. "
              + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
              + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
              + paramMap.keySet());
        }
        //多参数,需要截取keyProperty的.之前部分,去paramMap拿对应值
        String paramName = keyProperty.substring(0, firstDot);
        if (paramMap.containsKey(paramName)) {
          String argParamName = omitParamName ? null : paramName;
          String argKeyProperty = keyProperty.substring(firstDot + 1);
          return entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty));
        } else if (singleParam) {
          return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
        } else {
          throw new ExecutorException("Could not find parameter '" + paramName + "'. "
              + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
              + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
              + paramMap.keySet());
        }
      }
    
    

    根据注释,我们能看懂个大概,之后就进入getAssignerForSingleParam方法:

     private Entry<String, KeyAssigner> getAssignerForSingleParam(Configuration config, ResultSetMetaData rsmd,
          int columnPosition, Map<String, ?> paramMap, String keyProperty, boolean omitParamName) {
        // Assume 'keyProperty' to be a property of the single param.
        String singleParamName = nameOfSingleParam(paramMap);
        String argParamName = omitParamName ? null : singleParamName;
        return entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty));
      }
    

    还记得之前说的遍历赋值逻辑么,也就是

    pair.getValue().forEach(x -> x.assign(rs, param));方法中赋值:
    

    我们来看下这个方法:

    
    protected void assign(ResultSet rs, Object param) {
          if (paramName != null) {
            // If paramName is set, param is ParamMap
            param = ((ParamMap<?>) param).get(paramName);
          }
          MetaObject metaParam = configuration.newMetaObject(param);
          try {
            if (typeHandler == null) {
              if (metaParam.hasSetter(propertyName)) {
                Class<?> propertyType = metaParam.getSetterType(propertyName);
                typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
                    JdbcType.forCode(rsmd.getColumnType(columnPosition)));
              } else {
                throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
                    + metaParam.getOriginalObject().getClass().getName() + "'.");
              }
            }
            if (typeHandler == null) {
              // Error?
            } else {
              Object value = typeHandler.getResult(rs, columnPosition);
              metaParam.setValue(propertyName, value);
            }
          } catch (SQLException e) {
            throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
                e);
          }
        }
      }
    

    这里就是借助一些辅助的类来进行反射赋值了,逻辑自己整理下不难。下面我们也过一下SelectKeyGenerator类。

    3. SelectKeyGenerator解析

    我们先继续来看构造方法:

    public class SelectKeyGenerator implements KeyGenerator {
    
      public static final String SELECT_KEY_SUFFIX = "!selectKey";
      private final boolean executeBefore;
      private final 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) {
              // Do not close keyExecutor.
              // The transaction will be closed by parent executor.
              Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
              List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
              if (values.size() == 0) {
                throw new ExecutorException("SelectKey returned no data.");
              } else if (values.size() > 1) {
                throw new ExecutorException("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 {
                    // no getter for the property - maybe just a single value object
                    // so try that
                    setValue(metaParam, keyProperties[0], values.get(0));
                  }
                } else {
                  handleMultipleProperties(keyProperties, metaParam, metaResult);
                }
              }
            }
          }
        } catch (ExecutorException e) {
          throw e;
        } catch (Exception e) {
          throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
        }
      }
    

    这里主要SQL之前还是之后的都会到相同的方法,方法内部就是新建一个Executor,然后向数据库发送一条简单的sql执行语句,返回内容则赋值到参数中。

    最后还剩一个NoKeyGenerator

    4. NoKeyGenerator解析

    public class NoKeyGenerator implements KeyGenerator {
    
      /**
       * A shared instance.
       * @since 3.4.3
       */
      public static final NoKeyGenerator INSTANCE = new NoKeyGenerator();
    
      @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
      }
    
    }
    

    这个类就比较简单了,没什么特殊作用。

    5. 今日总结

    今天我们接触了关于KeyGenerator的源码,理解了他们的作用,我们再来做下小结:

    Jdbc3KeyGenerator:只能在执行SQL之后,它是一起的,通过业务SQL的执行从中获取数据库自增主键信息并匹配复制
    SelectKeyGenerator: 可以在执行SQL之前或之后运行,都是通过另起访问一个简单的SQL查询到值并进行注入
    NoKeyGenerator: 什么操作都没有

    相关文章

      网友评论

          本文标题:MyBatis印象阅读之KeyGenerator解析

          本文链接:https://www.haomeiwen.com/subject/nlomjctx.html