写在前面
在这篇文章中分析了数据查询和插入的过程,其中在插入过程分析中,涉及了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) {
}
}