mybatis-KeyGenerator

1. 概述

本文,我们来分享 SQL 执行的第三部分,keygen 包。整体类图如下:mybatis-KeyGenerator

  • 我们可以看到,整体是以 KeyGenerator 为核心。所以,本文主要会看到的就是 KeyGenerator 对自增主键的获取。

2. KeyGenerator

org.apache.ibatis.executor.keygen.KeyGenerator ,主键生成器接口。代码如下:

// KeyGenerator.java

public interface KeyGenerator {

// SQL 执行前
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

// SQL 执行后
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}
  • 可在 SQL 执行之前或之后,进行处理主键的生成。
  • 实际上,KeyGenerator 类的命名虽然包含 Generator ,但是目前 MyBatis 默认的 KeyGenerator 实现类,都是基于数据库来实现主键自增的功能。
  • parameter 参数,指的是什么呢?以下面的方法为示例:

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert({"insert into country (countryname,countrycode) values (#{countryname},#{countrycode})"})
    int insertBean(Country country);
    • 上面的,country 方法参数,就是一个 parameter 参数。
    • KeyGenerator 在获取到主键后,会设置回 parameter 参数的对应属性。

KeyGenerator 有三个子类,如下图所示:mybatis-KeyGenerator

  • 具体的,我们下面逐小节来分享。

3. Jdbc3KeyGenerator

org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator ,实现 KeyGenerator 接口,基于 Statement#getGeneratedKeys() 方法的 KeyGenerator 实现类,适用于 MySQL、H2 主键生成。

3.1 构造方法

// Jdbc3KeyGenerator.java

/**
* A shared instance.
*
* 共享的单例
*
* @since 3.4.3
*/
public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
  • 单例。

3.2 processBefore

@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
}
  • 空实现。因为对于 Jdbc3KeyGenerator 类的主键,是在 SQL 执行后,才生成。

3.3 processAfter

// Jdbc3KeyGenerator.java

@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
  • 调用 #processBatch(Executor executor, MappedStatement ms, Statement stmt, Object parameter) 方法,处理返回的自增主键。单个 parameter 参数,可以认为是批量的一个特例。

3.4 processBatch

// Jdbc3KeyGenerator.java

public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// <1> 获得主键属性的配置。如果为空,则直接返回,说明不需要主键
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
ResultSet rs = null;
try {
// <2> 获得返回的自增主键
rs = stmt.getGeneratedKeys();
final Configuration configuration = ms.getConfiguration();
if (rs.getMetaData().getColumnCount() >= keyProperties.length) {
// <3> 获得唯一的参数对象
Object soleParam = getSoleParameter(parameter);
if (soleParam != null) {
// <3.1> 设置主键们,到参数 soleParam 中
assignKeysToParam(configuration, rs, keyProperties, soleParam);
} else {
// <3.2> 设置主键们,到参数 parameter 中
assignKeysToOneOfParams(configuration, rs, keyProperties, (Map<?, ?>) parameter);
}
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
} finally {
// <4> 关闭 ResultSet 对象
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// ignore
}
}
}
}
  • <1> 处,获得主键属性的配置。如果为空,则直接返回,说明不需要主键。
  • 【重要】<2> 处,调用 Statement#getGeneratedKeys() 方法,获得返回的自增主键。
  • <3> 处,调用 #getSoleParameter(Object parameter) 方法,获得唯一的参数对象。详细解析,先跳到 「3.4.1 getSoleParameter」 。
    • <3.1> 处,调用 #assignKeysToParam(...) 方法,设置主键们,到参数 soleParam 中。详细解析,见 「3.4.2 assignKeysToParam」 。
    • <3.2> 处,调用 #assignKeysToOneOfParams(...) 方法,设置主键们,到参数 parameter 中。详细解析,见 「3.4.3 assignKeysToOneOfParams」 。
  • <4> 处,关闭 ResultSet 对象。

3.4.1 getSoleParameter

// Jdbc3KeyGenerator.java

/**
* 获得唯一的参数对象
*
* 如果获得不到唯一的参数对象,则返回 null
*
* @param parameter 参数对象
* @return 唯一的参数对象
*/
private Object getSoleParameter(Object parameter) {
// <1> 如果非 Map 对象,则直接返回 parameter
if (!(parameter instanceof ParamMap || parameter instanceof StrictMap)) {
return parameter;
}
// <3> 如果是 Map 对象,则获取第一个元素的值
// <2> 如果有多个元素,则说明获取不到唯一的参数对象,则返回 null
Object soleParam = null;
for (Object paramValue : ((Map<?, ?>) parameter).values()) {
if (soleParam == null) {
soleParam = paramValue;
} else if (soleParam != paramValue) {
soleParam = null;
break;
}
}
return soleParam;
}
  • <1> 处,如下可以符合这个条件。代码如下:

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert({"insert into country (countryname,countrycode) values (#{country.countryname},#{country.countrycode})"})
    int insertNamedBean(@Param("country") Country country);
  • <2> 处,如下可以符合这个条件。代码如下:

    @Options(useGeneratedKeys = true, keyProperty = "country.id")
    @Insert({"insert into country (countryname, countrycode) values (#{country.countryname}, #{country.countrycode})"})
    int insertMultiParams_keyPropertyWithWrongParamName2(@Param("country") Country country,
    @Param("someId") Integer someId);
    • 虽然有 country 和 someId 参数,但是最终会被封装成一个 parameter 参数,类型为 ParamMap 类型。为什么呢?答案在 ParamNameResolver#getNamedParams(Object[] args) 方法中。
    • 如果是这个情况,获得的主键,会设置回 country 的 id 属性,因为注解上的 keyProperty = "country.id" 配置。
上一篇:openframeworks 设置不显示控制台窗口


下一篇:A good debug parameter - sap-ds-debug=true