mybatis之通过KeyGenerator生成主键

写在前面

在这里插入图片描述

这篇文章中分析了数据查询和插入的过程,其中在插入过程分析中,涉及了org.apache.ibatis.executor.keygen,KeyGenerator主键生成器,但是并没有详细讲解,本文作为补充,分析此处。
想要系统学习的,可以参考这篇文章,重要!!!

1:简单说明

在设计数据库时,外键(虽然大部分是逻辑上的)的关联是非常常见的,那么,这种情况在进行数据库插入时肯定就需要知道关联表的主键,获取后进行存储,对于像hibernate这种全自动的ORM框架,在映射配置完毕后,框架本身在进行保存会自动的进行这个操作,但是对于像mybaits这种半自动化的ORM框架,这个过程并不能自动进行,需要开发人员自己获取生成的被关联表主键值,然后手动设置到关联表的逻辑外键中,mybaits为了解决这个问题,提供了org.apache.ibatis.executor.keygen.KeyGenerator接口,该接口源码如下:

org.apache.ibatis.executor.keygen.KeyGenerator
public interface KeyGenerator {
  // 在执行数据更新前生成主键
  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
  // 在执行数据更新后生成主键
  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}

其实现类如下图:
在这里插入图片描述
下面我们就通过这3个实现类来进行分析。

2:Jdbc3KeyGenerator

如下可能的配置会使用Jdbc3KeyGenerator:

<insert id="insertOne" parameterMap="parameterMap1515" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    insert into test_extend_and_automapping (`myname`, `myage`)
    values (#{myname}, #{myage})
</insert>

这里使用了useGeneratedKeys="true"代表我们希望在插入数据库之后获取生成的主键值,因为是在操作数据库之后获取主键,因此processBefore方法是不需要实现的,所以给出的是空实现,源码如下:

org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBefore
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  // do nothing
}

因此我们只需要看下procesAfter的源码就行了,如下:

org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processAfter
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  // 存储传入参数信息的集合
  List<Object> parameters = new ArrayList<Object>();
  // 添加传入的参数,如可能:TestExtendAndAutoMapping{id=null, myname='7868c188-241e-491a-b951-9624efc2044c', myage=24}
  // 注意此时id还是null,通过后续的操作就会赋值,然后在业务代码中就可以获取使用了
  parameters.add(parameter);
  // 处理
  // 2021-08-28 10:40:36
  processBatch(ms, stmt, parameters);
}

2021-08-28 10:40:36处源码如下:

org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBatch
public void processBatch(MappedStatement ms, Statement stmt, List<Object> parameters) {
  ResultSet rs = null;
  try {
    // 获取JDBC的ResultSet对象,封装有数据库生成的主键信息(因此,mybaits最终还是要依赖于JDBC的机制)
    rs = stmt.getGeneratedKeys();
    // 获取全局配置文件对应的Configuration对象
    final Configuration configuration = ms.getConfiguration();
    // 获取类型处理器注册器
    final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    // 获取keyProperty属性配置的值,这里可以认为只有一个
    final String[] keyProperties = ms.getKeyProperties();
    // 获取结果集元信息对象
    final ResultSetMetaData rsmd = rs.getMetaData();
    TypeHandler<?>[] typeHandlers = null;
    if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
      // 默认parameters为1,所以认为循环一次即可
      for (Object parameter : parameters) {
        // 这里也认为只会一次为true
        if (!rs.next()) break; 
        // 将传入参数封装为MetaObject方便操作,比如操作其属性值等
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        // 获取类型处理器,以便赋值属性值
        if (typeHandlers == null) typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties);
        // 填充生成的主键值到传入对象中
        // 2021-08-28 11:04:58
        populateKeys(rs, metaParam, keyProperties, typeHandlers);
      }
    }
  } catch (Exception e) {
    throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
  } finally {
    if (rs != null) {
      try {
        rs.close();
      } catch (Exception e) {
      }
    }
  }
}

2021-08-28 11:04:58处源码如下:

org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#populateKeys
private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException {
  // 循环keyProperties通过ResultSet为每个主键值赋值,一般为1,
  // 多个时为联合主键情况
  for (int i = 0; i < keyProperties.length; i++) {
    // 获取当前处理属性的类型处理器,从数据库值->对象属性值
    TypeHandler<?> th = typeHandlers[i];
    // 类型处理器不为null,才处理,否则无法转换,因此不处理
    if (th != null) {
      // 获取属性值
      Object value = th.getResult(rs, i + 1);
      // 赋值,如下是赋值前和赋值后的参数值
      // 赋值前:TestExtendAndAutoMapping{id=null, myname='fe47585e-37b5-429c-bf28-8dc41b6cd737', myage=40}
      // 赋值后:TestExtendAndAutoMapping{id=5, myname='fe47585e-37b5-429c-bf28-8dc41b6cd737', myage=40}
      metaParam.setValue(keyProperties[i], value);
    }
  }
}

3:SelectKeyGenerator

3.1:processBefore

如下配置会执行该方法,在操作数据库之前,生成主键值,最终使用该主键值进行数据库操作:

<insert id="insertByQueryParamter" parameterMap="myParameterMap">
    <selectKey keyProperty="id" keyColumn="id"
               resultType="java.lang.String" order="BEFORE">
        select uuid()
    </selectKey>
    INSERT INTO `test_selectkey`
            (`id`,
            `myname`,
            `myage`)
    VALUES (#{id},
            #{myname},
            #{myage})
</insert>

在操作数据库之前会通过select uuid()生成主键值,并设置到对应的属性中。
源码如下:

org.apache.ibatis.executor.keygen.SelectKeyGenerator#processBefore
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  // 当order="BEFORE"时,executeBefore为true
  if (executeBefore) {
    // 生成主键,并设置到参数的对应属性中,用于后续数据库的操作
    // 2021-08-28 11:20:57
    processGeneratedKeys(executor, ms, parameter);
  }
}

2021-08-28 11:20:57处具体参考3.3:处理生成的主键值

3.2:processAfter

如下的配置会执行该方法,在数据库操作完成之后,获取由数据库生成的主键

<insert id="insertOneSelectKeyAfter" parameterMap="parameterMap1515">
    <selectKey keyProperty="id"
               keyColumn="id"
               order="AFTER"
               resultType="java.lang.Integer">
        select LAST_INSERT_ID()
    </selectKey>
    insert into test_extend_and_automapping (`myname`, `myage`)
    values (#{myname}, #{myage})
</insert>

源码如下:

org.apache.ibatis.executor.keygen.SelectKeyGenerator#processAfter
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  // 当设置<selectKey/>标签的order="AFTER"时这里为true
  if (!executeBefore) {
    // 2021-08-28 19:26:27
    processGeneratedKeys(executor, ms, parameter);
  }
}

2021-08-28 19:26:27处是处理生成的主键,具体参考3.3:处理生成的主键值

3.3:处理生成的主键值

org.apache.ibatis.executor.keygen.SelectKeyGenerator#processGeneratedKeys
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
  try {
    if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
      // 获取配置的属性值,即<selectKey>标签中配置的keyProperty="id"
      String[] keyProperties = keyStatement.getKeyProperties();
      // 获取全局配置文件对应的Configuration对象
      final Configuration configuration = ms.getConfiguration();
      // 将传入参数封装为MetaObject对象,方便操作,如设置属性
      final MetaObject metaParam = configuration.newMetaObject(parameter);
      if (keyProperties != null) {
        // 获取执行器,用于执行在<selectKey/>标签中设置的生成主键的sql语句
        Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
        // keyStatement:是对<selectKey/>标签的封装
        // 执行<selectKey/>对应的MappedStatement,生成主键
        // 如select UUID()结果为db6fbe60-07af-11ec-978a-1b6197d25533
        List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
        // 判断长度只能为1,这里使用List接收,是因为底层如此
        // 这里只能通过判断元素个数来进行控制
        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) {
            // 如果是传入的对象的当前属性有getter方法,一般获取的是
            // 字符串,所有肯定没有
            if (metaResult.hasGetter(keyProperties[0])) {
              setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
            } else {
              // 设置属性值
              // metaParam:用户传入的参数对象
              // keyProperties[0]:要设置的属性值
              // values.get(0):生成的主键值
              // 将生成的主键值设置到传入参数对象的属性中
              // 如下是设置前,设置后的值:
              // 设置前:{"myage":19,"myname":"379bff59-eb6d-4b03-95d4-571379e9d13f"}
              // 设置后:{"id":"db6fbe60-07af-11ec-978a-1b6197d25533","myage":19,"myname":"379bff59-eb6d-4b03-95d4-571379e9d13f"}
              // 2021-08-28 11:37:25
              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);
  }
}

021-08-28 11:37:25处是调用传入参数对象的setter方法设置属性值,源码如下:

org.apache.ibatis.executor.keygen.SelectKeyGenerator#setValue
private void setValue(MetaObject metaParam, String property, Object value) {
  // 有setter方法的话
  if (metaParam.hasSetter(property)) {
    // 通过setter方法设置属性值
    metaParam.setValue(property, value);
  } else {
    // 没有setter方法则抛出异常,并给出相应的异常信息
    // 告知在哪个类里的哪个属性没有setter方法
    throw new ExecutorException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
  }
}

4:NoKeyGenerator

不设置主键值,也不获取数据库生成的主键值的情况,为了保持代码的通用性,这里也给出一个实现类处理这种情况,源码如下:

org.apache.ibatis.executor.keygen.NoKeyGenerator
public class NoKeyGenerator implements KeyGenerator {

  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  }

  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值