1. 概述
本文,我们来分享 SQL 执行的第三部分,keygen
包。整体类图如下:
- 我们可以看到,整体是以 KeyGenerator 为核心。所以,本文主要会看到的就是 KeyGenerator 对自增主键的获取。
2. KeyGenerator
org.apache.ibatis.executor.keygen.KeyGenerator
,主键生成器接口。代码如下:
// KeyGenerator.java |
- 可在 SQL 执行之前或之后,进行处理主键的生成。
- 实际上,KeyGenerator 类的命名虽然包含 Generator ,但是目前 MyBatis 默认的 KeyGenerator 实现类,都是基于数据库来实现主键自增的功能。
-
parameter
参数,指的是什么呢?以下面的方法为示例:- 上面的,
country
方法参数,就是一个parameter
参数。 - KeyGenerator 在获取到主键后,会设置回
parameter
参数的对应属性。
- 上面的,
KeyGenerator 有三个子类,如下图所示:
- 具体的,我们下面逐小节来分享。
3. Jdbc3KeyGenerator
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
,实现 KeyGenerator 接口,基于 Statement#getGeneratedKeys()
方法的 KeyGenerator 实现类,适用于 MySQL、H2 主键生成。
3.1 构造方法
// Jdbc3KeyGenerator.java |
- 单例。
3.2 processBefore
|
- 空实现。因为对于 Jdbc3KeyGenerator 类的主键,是在 SQL 执行后,才生成。
3.3 processAfter
// Jdbc3KeyGenerator.java |
- 调用
#processBatch(Executor executor, MappedStatement ms, Statement stmt, Object parameter)
方法,处理返回的自增主键。单个parameter
参数,可以认为是批量的一个特例。
3.4 processBatch
// Jdbc3KeyGenerator.java |
<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 |
-
<1>
处,如下可以符合这个条件。代码如下: -
<2>
处,如下可以符合这个条件。代码如下:- 虽然有
country
和someId
参数,但是最终会被封装成一个parameter
参数,类型为 ParamMap 类型。为什么呢?答案在ParamNameResolver#getNamedParams(Object[] args)
方法中。 - 如果是这个情况,获得的主键,会设置回
country
的id
属性,因为注解上的keyProperty = "country.id"
配置。 - 😈 此处比较绕,也相对用的少。
- 虽然有
-
<3>
处,如下可以符合这个条件。代码如下:- 相比
<2>
的示例,主要是keyProperty = "id"
的修改,和去掉了@Param("someId") Integer someId
参数。 - 实际上,这种情况,和
<1>
是类似的。
- 相比
- 三种情况,
<2>
和<3>
有点复杂,胖友实际上,理解<1>
即可。
3.4.2 assignKeysToParam
// Jdbc3KeyGenerator.java |
<1>
处,包装成 Collection 对象。通过这样的方式,使单个param
参数的情况下,可以统一。-
<2>
处,遍历paramAsCollection
数组:<2.1>
处, 顺序遍历rs
,相当于把当前的 ResultSet 对象的主键们,赋值给obj
对象的对应属性。<2.2>
处,创建 MetaObject 对象,实现对obj
对象的属性访问。-
<2.3>
处,调用#getTypeHandlers(...)
方法,获得 TypeHandler 数组。代码如下:// Jdbc3KeyGenerator.java
private TypeHandler<?>[] getTypeHandlers(TypeHandlerRegistry typeHandlerRegistry, MetaObject metaParam, String[] keyProperties, ResultSetMetaData rsmd) throws SQLException {
// 获得主键们,对应的每个属性的,对应的 TypeHandler 对象
TypeHandler<?>[] typeHandlers = new TypeHandler<?>[keyProperties.length];
for (int i = 0; i < keyProperties.length; i++) {
if (metaParam.hasSetter(keyProperties[i])) {
Class<?> keyPropertyType = metaParam.getSetterType(keyProperties[i]);
typeHandlers[i] = typeHandlerRegistry.getTypeHandler(keyPropertyType, JdbcType.forCode(rsmd.getColumnType(i + 1)));
} else {
throw new ExecutorException("No setter found for the keyProperty '" + keyProperties[i] + "' in '"
+ metaParam.getOriginalObject().getClass().getName() + "'.");
}
}
return typeHandlers;
}- x
<2.4>
处,调用#populateKeys(...)
方法,填充主键们。详细解析,见 「3.5 populateKeys」 。
3.4.3 assignKeysToOneOfParams
// Jdbc3KeyGenerator.java |
<1>
处,需要有.
。例如:@Options(useGeneratedKeys = true, keyProperty = "country.id")
。<2>
处,获得真正的参数值。<3>
处,获得主键的属性的配置。<4>
处,调用#assignKeysToParam(...)
方法,设置主键们,到参数param
中。所以,后续流程,又回到了 「3.4.2」 咧。- 关于这个方法,胖友自己模拟下这个情况,调试下会比较好理解。😈 当然,也可以不理解,嘿嘿。
3.5 populateKeys
// Jdbc3KeyGenerator.java |
- 代码比较简单,胖友看下注释即可。
4. SelectKeyGenerator
org.apache.ibatis.executor.keygen.SelectKeyGenerator
,实现 KeyGenerator 接口,基于从数据库查询主键的 KeyGenerator 实现类,适用于 Oracle、PostgreSQL 。
4.1 构造方法
// SelectKeyGenerator.java |
4.2 processBefore
// SelectKeyGenerator.java |
- 调用
#processGeneratedKeys(...)
方法。
4.3 processAfter
// SelectKeyGenerator.java |
- 也是调用
#processGeneratedKeys(...)
方法。
4.4 processGeneratedKeys
// SelectKeyGenerator.java |
<1>
处,有查询主键的 SQL 语句,即keyStatement
对象非空。<2>
处,创建执行器,类型为 SimpleExecutor 。- 【重要】
<3>
处,调用Executor#query(...)
方法,执行查询主键的操作。😈 简单脑暴下,按照 SelectKeyGenerator 的思路,岂不是可以可以接入 SnowFlake 算法,从而实现分布式主键。 <4.1>
处,查不到结果,抛出 ExecutorException 异常。<4.2>
处,查询的结果过多,抛出 ExecutorException 异常。-
<4.3>
处,创建 MetaObject 对象,访问查询主键的结果。-
<4.3.1>
处,单个主键,调用#setValue(MetaObject metaParam, String property, Object value)
方法,设置属性到metaParam
中,相当于设置到parameter
中。代码如下:// SelectKeyGenerator.java
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() + ".");
}
}- 简单,胖友自己瞅瞅。
-
<4.3.2>
处,多个主键,调用#handleMultipleProperties(String[] keyProperties, MetaObject metaParam, MetaObject metaResult)
方法,遍历,进行赋值。代码如下:// SelectKeyGenerator.java
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]));
}
}
}- 最终,还是会调用
#setValue(...)
方法,进行赋值。
- 最终,还是会调用
-
4.5 示例
5. NoKeyGenerator
org.apache.ibatis.executor.keygen.NoKeyGenerator
,实现 KeyGenerator 接口,空的 KeyGenerator 实现类,即无需主键生成。代码如下:
// NoKeyGenerator.java |