Mybatis 主键回显 KeyGenerator原理

这篇文章研究下 Mybatis 配置主键回显相关功能。

本篇文章将以以下几个问题切入:

  1. Mybatis 如何 配置主键自增回显?
  2. JDBC 主键回显用法?
  3. 对于不支持自增主键数据库,Mybatis 有怎么解决这个问题?
  4. Mybatis 有哪几种主键生成方式?

例子

  1. JDBC 自增主键例子
    对于数据库支持自增主键的库,例如:Mysql:
            Connection conn = DriverManager.getConnection(url, "root", "123456");
            String[] columnNames = {"ids", "name"};
            PreparedStatement stmt = conn.prepareStatement(sql, columnNames);
            stmt.setString(1, "test jdbc3 ");
            stmt.executeUpdate();
            ResultSet rs = stmt.getGeneratedKeys();
            int id = 0;
            if (rs.next()) {
                id = rs.getInt(1);
                System.out.println("----------" + id);
            }

即当使用 prepareStatement 或者 Statement时候,可以通过getGeneratedKeys 获取 当条插入语句的自增而成的主键。
当然还有其他几种方式,原理是 数据库端返回一个LAST_INSERT_ID,这个跟auto_increment_id 强相关。
其他几种方式可以参考oracle文档:https://docs.oracle.com/cd/E17952_01/connector-j-en/connector-j-usagenotes-last-insert-id.html

Mybtis 对于数据库主键回显,主要分为两种,一种是数据库支持自增主键字段,另一种是数据库不支持方式即手动指定方式。

  1. 数据库支持主键回显,例如 Mysql,postgresql 使用 Jdbc3KeyGenerator 进行数据库回显:
    在 insert 中配置 useGeneratedKeys="true" keyColumn="id" keyProperty="id" ,即当插入或者批量插入后,成功后即可返回配置id:
    <insert id="insertWithGenertoorKey" useGeneratedKeys="true"  keyColumn="id" keyProperty="id">
        insert into department (name) values (#{department.name})
    </insert>
  1. 数据库不支持主键回显,例如 oracle,DB2。则使用 selectKey 先查出最大id,而后进行插入操作:
    <insert id="insertWithSelectKeyGenertoorKey">
        <selectKey keyProperty="id" resultType="long" order="BEFORE">
            SELECT if(max(id) is null,1,max(id)+10) as newId FROM department
        </selectKey>
        insert into department (id, name) values (#{id}, #{department.name})
    </insert>

分析

下面以 上面三个用法为基础,分析Mybatis 主键自增回显原理。
当 Mybatis 解析 xml节点是,读到 insert 有配置时,会判断是否 有配置 useGeneratedKeys,如果有则会使用 Jdbc3KeyGenerator 作为sql回显,否则会以 NoKeyGenerator 作为主键回显。
当执行插入数据操作时,执行以下逻辑:

  1. 首先会进入 MapperMethodexecute 方法,判断执行类型
  2. 当上一步判断是 INSERT 时,则会 进入 DefaultSqlSessioninsert 方法,随后进入 其 update 方法。
  3. 进入 Executorupdate 方法,配置完 ErrorContext 后,进入 doUpdate 方法:
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }
  1. MybatisKeyGenerator 使用是在 PreparedStatementHandlerupdate 中:
  @Override
  public int update(Statement statement) throws SQLException {
    // 获取 preparedStatement
    PreparedStatement ps = (PreparedStatement) statement;
   // 执行 语句
    ps.execute();
    // 获取影响行
    int rows = ps.getUpdateCount();
    // 获取参数
    Object parameterObject = boundSql.getParameterObject();
    // 获取 KeyGenerator,自增则通过 jdbc获取,否则就通过selectKey 查询获取
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    // 回填 配置星到 参数中
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

Mybatis 对于 KeyGenerator 主要集中在上面的update 代码中,下面看看 三种 KeyGenerator 含义:

KeyGenerator

KeyGenerator 接口 中 只有 两个方法:

  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

processBefore :主要用于 插入前操作,即获取和设置主键(不支持主键自增)
processAfter:用于插入后对主键进行回显到参数中

  1. NoKeyGenerator
    这个类 是对 KeyGenerator 的空实现,主要是不配置 generatorKey,所以 processAfterprocessBefore 事实上不需要进行任何操作
  2. Jdbc3KeyGenerator
    使用 JDBC 方式获取自增主键,其 processBefore 是空实现,只实现了 processAfter
  @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) {
      // 多参数或者单参数使用 @Param  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 {
      // 没有 用 @Param注解的单参数  Single param without @Param
      assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
    }
  }

从上面看到,Mybatis 通过判断三种情况对 数据进行回填,从而使用不同回填,具体不在分析。

Jdbc mysql 连接驱动 中 getGeneratedKeys 获取有以下几个分析点:

  1. 为什么 支持 id 回显? 通过 返回的 LAST_INSERT_ID 返回。
  2. 为什么 批量插入可以 给所有对象返还id? 通过获取最后一个id,并且知道影响行数遍历设值
  3. 为什么批量 insert on duplicate key update 不能回填所有id? 因为insert on duplicate key update这种 方式 有多种情况,分别会返回0(存在但数值一致),1(插入),2(更新), 不是固定的1,所以不能通过遍历设值。

具体可以看看这篇文章,关于 Jdbc Mysql 驱动的 getGeneratedKeys深入分析Mybatis 使用useGeneratedKeys获取自增主键

  1. 如果数据库不支持 自增主键,Mybatis 提供一种 SelectKey 方式,即先查出,再将id填充进参数进行插入操作:
  @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
          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);
    }
  }

对于 SelectKeyGenerator 方式进行插入,则需要在 配置 SelectKey 时候指定 order, 如果没有指定,则默认为 BEFORE
order="BEFORE"BEFORE 则为在 插入前设值,即 上述代码中 executeBeforetrue

processGeneratedKeys 方法主要意思为 获取一个 Executor ,而后对 当前 MappedStatement 进行查询操作,最终返回到 List 的value中,并设值到 parameter 中。

以上,就是 MybatisKeyGenerator 主键回显原理。

觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,一起研究Mybatis:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章