KeyGenerator

作者: 93张先生 | 来源:发表于2020-09-02 10:45 被阅读0次

    KeyGenerator

    KeyGenerator 是用来获取 insert、update 语句 获取数据库主键 id 的。

    insert 语句并不会返回自动生成的主键,而是返回插入记录的条数。如果业务
    逻辑需要获取插入记录时产生的自增主键,则可以使用 Mybatis 提供的 KeyGenerator 接口。

    不同的数据库产品对应的主键生成策略不一样,例如, Oracle 、DB2 等数据库产品是通过 sequence 实现自增 id 的,在执行 insert 语句之前必须明确指定主键的值。

    而 MySQL, Postgresql 等数据库在执行 insert 吾句时,可以不指定主键,在插入过程中由数据库自动生成自增主键。、

    KeyGenerator 接口针对这些不同的数据库产品提供了对应的处理方法。

    public interface KeyGenerator {
    
    
      // 在执行 insert 之前执行,设置属性 order = "BEFORE"
      void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
    
      // 在执行 insert 之后执行,设置属性 order = "After"
      void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
    
    }
    

    UML 类图

    image.png

    NoKeyGenerator

    NoKeyGenerator 虽然实现了 KeyGenerator 接口,但是其中的 processBefore() 方法和 processAfter() 方法都是空实现。

    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
      }
    
    }
    

    Jdbc3KeyGenerator

    Jdbc3KeyGenerator 用于取回数据库 生成的自增 id,对应 mybatis-config.xml 配置文件中的 useGeneratedKeys 全局配置,以及 insert 节点中 useGeneratedKeys 属性。

    mapper 配置
    <insert id="test_insert" useGeneratedKeys="true" keyProperty=”id”〉
        INSERT INTO t_user(username , pwd) VALUES
         <foreach item= "item" collection=” list” separator=”,”>
            (#(item.username}, #(item.pwd})
         </foreach>
     </insert>
    
    被调用的地方

    XMLStatementBuilder 主要用来解析 mapper.xml 中的 insert 、update、delete、select 节点。
    XMLStatementBuilder.parseStatementNode() 方法,用来解析 mapper.xml 文件中的节点。其中 insert 节点中配置了获取 主键 Id 的 useGeneratedKeys 的方式。

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        KeyGenerator keyGenerator;
        // 获取 <selectKey> 节点对应的 SelectKeyGenerator 的 Id
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        //检测 SQL 节点中 是否 配置了 <selectKey> 节点、SQL 节点的 useGeneratedKeys 属性值;
        // <insert> <update> 节点中的 <selectKey> 节点指定了获取数据库主键的方式;
        if (configuration.hasKeyGenerator(keyStatementId)) {
          keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
          // 根据 SQL 节点的 useGeneratedKeys 属性值、mybatis-config.xml 中全局的 useGeneratedKeys 配置,以及是否是 insert 语句,决定使用 keyGenerator 的具体接口实现
          keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
              configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
              ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
    

    详解 Jdbc3KeyGenerator

    Jdbc3KeyGenerator.processBefore() 方法是空实现,只实现了 processAfter() 方法,该方法会调
    Jdbc3 KeyGenerator. processBatch() 方法将 SQL 语句执行后生成的主键记录到用户传递的实参中。

    在 PreparedStatement 执行完 update 之后,将 SQL 语句执行后生成的主键记录到用户传递的时参数中。一般情况下,对应单行插入操作,传入的实参是一个 JavaBean 对象 或是 Map 对象,则将对象对应一次插入操作的内容;对于多行插入, 传入的实参可以是对象或 Map 对象的数组或集合,集合每一个元素都对应一次插操作。

    public class Jdbc3KeyGenerator implements KeyGenerator {
    
      /**
       * A shared instance.
       *
       * @since 3.4.3
       */
      public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
    
      private static final String MSG_TOO_MANY_KEYS = "Too many keys are generated. There are only %d target objects. "
          + "You either specified a wrong 'keyProperty' or encountered a driver bug like #1523.";
    
      @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);
      }
    
      /**
       * 在 PreparedStatement 执行完 update 之后,将 SQL 语句执行后生成的主键记录到用户传递的时参数中。
       * 一般情况下,对应单行插入操作,传入的实参是一个 JavaBean 对象 或是 Map 对象,则将对象对应一次插入操作的内容;
       * 对于多行插入, 传入的实参可以是对象或 Map 对象的数组或集合,集合每一个元素都对应一次插操作。
       * @param ms
       * @param stmt
       * @param 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);
        }
      }
    
      /**
       * 处理不同类型 结果实参;Map 类型、ArrayList 类型 等等
       * @param configuration
       * @param rs sql 执行结果集合
       * @param rsmd ResultSet 结果集合的每列的元属性,
       * @param keyProperties  指定的自增主键 Id 对应 Java 对象属性
       * @param parameter sql 数据库执行之后,数据库传回的参数
       * @throws SQLException
       */
      @SuppressWarnings("unchecked")
      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);
        }
      }
    
      /**
       * 处理 Java 对象或者 Bean 对象,非集合对象,单条数据情况
       * @param configuration
       * @param rs
       * @param rsmd
       * @param keyProperties
       * @param parameter
       * @throws SQLException
       */
      private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
          String[] keyProperties, Object parameter) throws SQLException {
        Collection<?> params = collectionize(parameter);
        if (params.isEmpty()) {
          return;
        }
        List<KeyAssigner> assignerList = new ArrayList<>();
        for (int i = 0; i < keyProperties.length; i++) {
          assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
        }
        Iterator<?> iterator = params.iterator();
        while (rs.next()) {
          if (!iterator.hasNext()) {
            throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
          }
          Object param = iterator.next();
          assignerList.forEach(x -> x.assign(rs, param));
        }
      }
    
      /**
       * 处理 List 类型,遍历数据库生成的主键结果集,并设置到 Parameters 集合对应的元素的属性中
       * @param configuration
       * @param rs
       * @param rsmd
       * @param keyProperties
       * @param paramMapList
       * @throws SQLException
       */
      private void assignKeysToParamMapList(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
          String[] keyProperties, ArrayList<ParamMap<?>> paramMapList) throws SQLException {
        Iterator<ParamMap<?>> iterator = paramMapList.iterator();
        List<KeyAssigner> assignerList = new ArrayList<>();
        long counter = 0;
        while (rs.next()) {
          if (!iterator.hasNext()) {
            throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
          }
          ParamMap<?> paramMap = iterator.next();
          if (assignerList.isEmpty()) {
            for (int i = 0; i < keyProperties.length; i++) {
              assignerList
                  .add(getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], keyProperties, false)
                      .getValue());
            }
          }
          assignerList.forEach(x -> x.assign(rs, paramMap));
          counter++;
        }
      }
    
      /**
       * 处理 Map 类型,遍历数据库生成的主键结果集,并设置到 Parameters 集合对应的元素的属性中
       * @param configuration
       * @param rs
       * @param rsmd
       * @param keyProperties
       * @param paramMap
       * @throws SQLException
       */
      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++) {
          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++;
        }
      }
    
      private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd,
          int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) {
        boolean singleParam = paramMap.values().stream().distinct().count() == 1;
        int firstDot = keyProperty.indexOf('.');
        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());
        }
        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());
        }
      }
    
      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));
      }
    
      private static String nameOfSingleParam(Map<String, ?> paramMap) {
        // There is virtually one parameter, so any key works.
        return paramMap.keySet().iterator().next();
      }
    
      /**
       * 参数对象集合化
       * @param param
       * @return
       */
      private static Collection<?> collectionize(Object param) {
        if (param instanceof Collection) {
          return (Collection<?>) param;
        } else if (param instanceof Object[]) {
          return Arrays.asList((Object[]) param);
        } else {
          return Arrays.asList(param);
        }
      }
    
      private static <K, V> Entry<K, V> entry(K key, V value) {
        // Replace this with Map.entry(key, value) in Java 9.
        return new AbstractMap.SimpleImmutableEntry<>(key, value);
      }
    
      private class KeyAssigner {
        private final Configuration configuration;
        private final ResultSetMetaData rsmd;
        private final TypeHandlerRegistry typeHandlerRegistry;
        private final int columnPosition;
        private final String paramName;
        private final String propertyName;
        private TypeHandler<?> typeHandler;
    
        protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName,
            String propertyName) {
          super();
          this.configuration = configuration;
          this.rsmd = rsmd;
          this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
          this.columnPosition = columnPosition;
          this.paramName = paramName;
          this.propertyName = propertyName;
        }
    
        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);
          }
        }
      }
    }
    
    
    mapper 配置
    <insert id="test_insert" useGeneratedKeys="true" keyProperty=”id”〉
        INSERT INTO t_user(username , pwd) VALUES
         <foreach item= "item" collection=” list” separator=”,”>
            (#(item.username}, #(item.pwd})
         </foreach>
     </insert>
    

    整个示例的执行流程

    image.png

    SelectKeyGenerator

    对于不支持自动生成自增主键的数据库,例如 Oracle 数据库,用户可以利用 MyBatis 提供 的 SelectkeyGenerator 来生成主键, SelectKeyGenerator 也可以实现类似于 Jdbc3KeyGenerator 提供的、获取数据库自动生成的主键的功能。

    SelectKeyGenerator 主要用于生成主键,它会执行映射配置文件中定义的<selectKey>节点的 SQL 语句,该语句会获取 insert 语句所需要的主键。

    mapper 配置
    <insert id="insertAuthor" parameterType="org.apache.ibatis.domain.blog.Author">
        <selectKey keyProperty="id" resultType="_int" order="BEFORE">
            <include refid="selectNum">
                <property name="num" value="1"/>
            </include>
        </selectKey>
        insert into Author (username,password,email,bio)
        values (#{username},#{password},#{email},#{bio})
    </insert>
    
    <sql id="selectNum">
        SELECT #{num}
    </sql>
    
    调用方法地方

    XMLStatementBuilder.parseStatementNode() 方法中的 processSelectKeyNodes() 方法,用来解析 mapper.xml 文件中 insert 节点中的 <selectKey> ,主要用来生成 Id 主键。

    // Parse selectKey after includes and remove them.
    // 处理<selectKey> 节点,来解决主键自增问题,
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    /**
       * 解析 <selectKey> 节点
       * @param id
       * @param parameterTypeClass
       * @param langDriver
       */
      private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
        // 获取全部的 <selectKey> 节点
        List<XNode> selectKeyNodes = context.evalNodes("selectKey");
        // 解析<selectKey>节点
        if (configuration.getDatabaseId() != null) {
          parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
        }
        parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
        // 移除<selectKey>节点
        removeSelectKeyNodes(selectKeyNodes);
      }
    
      /**
       * 解析 <selectKey> 节点
       * @param parentId
       * @param list
       * @param parameterTypeClass
       * @param langDriver
       * @param skRequiredDatabaseId
       */
      private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
        for (XNode nodeToHandle : list) {
          String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
          String databaseId = nodeToHandle.getStringAttribute("databaseId");
          if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
            parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
          }
        }
      }
    
      /**
       * 解析 <selectKey> 节点
       * @param id
       * @param nodeToHandle
       * @param parameterTypeClass
       * @param langDriver
       * @param databaseId
       */
      private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
        // 获取 <selectKey> 节点的多个属性
        String resultType = nodeToHandle.getStringAttribute("resultType");
        Class<?> resultTypeClass = resolveClass(resultType);
        StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
        String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
        boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
    
        //defaults
        boolean useCache = false;
        boolean resultOrdered = false;
        KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
        Integer fetchSize = null;
        Integer timeout = null;
        boolean flushCache = false;
        String parameterMap = null;
        String resultMap = null;
        ResultSetType resultSetTypeEnum = null;
    
        // 通过 LanguageDriver.createSqlSource() 方法生成 SqlSource
        SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
        // <selectKey> 节点中只能配置 select 吾句
        SqlCommandType sqlCommandType = SqlCommandType.SELECT;
    
        // 创建 MappedStatement 对象,并保存到 Configuration.mappedStatements 集合
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
    
        id = builderAssistant.applyCurrentNamespace(id, false);
    
        MappedStatement keyStatement = configuration.getMappedStatement(id, false);
        // 创建 <selectKey> 节点对应的 KeyGenerator,添加到 Configuration.keyGenerators 集合;
        configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
      }
    
    
    public class SelectKeyGenerator implements KeyGenerator {
    
      public static final String SELECT_KEY_SUFFIX = "!selectKey";
      // 标识 <selectKey>节点中定义的 SQL 语句是在 insert 吾句之前执行还是之后执行
      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);
        }
      }
      /**
       * <insert id="insert">
       *     <!-- 在 insert 语句执行之前,先通过执行 <selectKey>节点对应的 Select 语句生成 insert 语句中使用的主键,也就是这里的 id -->
       *    <selectKey keyProperty=”id” result Type=” int” order=” BEFORE” >
       *      SELECT FLOOR(RAND() * 10000) ;
       *    </selectKey>
       *    insert into T_USER (ID , username ,pwd) values ( #{id} , #{username} , #{pwd) )
       * </insert>
       */
      /**
       * processGeneratedKeys() 方法会执行<selectKey>节点中配置的 SQL 语句,获取 insert 语句中用到的主键井映射成对象,然后按照配置,将主键对象中对应的属性设置到用户参数中。
       * @param executor
       * @param ms
       * @param parameter
       */
      private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
        try {
          if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
            // 获取 selectKey 节点的 keyProperties 配置的属性名称,它表示主键对应的属性
            String[] keyProperties = keyStatement.getKeyProperties();
            final Configuration configuration = ms.getConfiguration();
            // 创建用户传入的实参对象对应的 metaObject 对象
            final MetaObject metaParam = configuration.newMetaObject(parameter);
            // Do not close keyExecutor.
            // The transaction will be closed by parent executor.
            // 创建 Executor 对象,并执行 keyStatement 字段中记录的 SQL 语句,并得到 主键对象
            Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
            List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
            // 检测 values 集合的长度,该集合长度只能为 1
            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
                  //如采主键对象不包含指定属性的 setter 方法,可能是一个基本类型,直接将主键对象设置到用户参数中
                  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);
        }
      }
    
      private void handleMultipleProperties(String[] keyProperties,
          MetaObject metaParam, MetaObject metaResult) {
        String[] keyColumns = keyStatement.getKeyColumns();
    
        if (keyColumns == null || keyColumns.length == 0) {
          // no key columns specified, just use the property names
          for (String keyProperty : keyProperties) {
            setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));
          }
        } else {
          if (keyColumns.length != keyProperties.length) {
            throw new ExecutorException("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 ExecutorException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
        }
      }
    }
    
    mapper 配置
    <insert id="insert">
       <!-- 在 insert 语句执行之前,先通过执行 <selectKey>节点对应的 Select 语句生成 insert 语句中使用的主键,也就是这里的 id -->
      <selectKey keyProperty=”id” result Type=” int” order=” BEFORE” >
        SELECT FLOOR(RAND()10000) ;
      </selectKey>
      insert into T_USER (ID , username ,pwd) values ( #{id} , #{username} , #{pwd) )
    </insert>
    
    整个示例的执行流程
    image.png

    相关文章

      网友评论

        本文标题:KeyGenerator

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