在Mybatis中,执行insert操作时,如果我们希望返回数据库生成的自增主键值,那么就需要使用到KeyGenerator对象。
需要注意的是,KeyGenerator的作用,是返回数据库生成的自增主键值,而不是生成数据库的自增主键值。返回的主键值放到哪儿呢?放到parameter object的主键属性上。
下面看看其接口定义。
接口定义还是比较简单的,就是在insert前、insert后,策略处理主键值。
Jdbc3KeyGenerator:用于处理数据库支持自增主键的情况,如MySQL的auto_increment。
NoKeyGenerator:空实现,不需要处理主键。
SelectKeyGenerator:用于处理数据库不支持自增主键的情况,比如Oracle的sequence序列。
上面都比较泛泛而谈,我们来点实际的,看看它们都是如何工作的。
1. JDBC实现insert后,返回自增主键值的原理
以上代码,仅作为演示使用。Mybatis是对JDBC的封装,其Jdbc3KeyGenerator类,就是使用上面的原理,来返回数据库生成的主键值的。
2. Jdbc3KeyGenerator源码解读
Mapper.Xml配置方式。
3. NoKeyGenerator源码解读
完全是空实现,没啥可说的。
4. SelectKeyGenerator的原理
在执行insert之前,先发起一个sql查询,将返回的序列值赋值给Student的stuId属性,然后再执行insert操作,这样表中的stud_id字段就有值了。order="BEFORE"表示insert前执行,比如取sequence序列值;order="AFTER"表示insert之后执行,比如使用触发器给主键stud_id赋值。比较简单,我就不再贴源码了。
注意:由于selectKey本身返回单个序列主键值,也就无法支持批量insert操作并返回主键id列表了。如果要执行批量insert,请选择使用for循环执行多次插入操作。
5. KeyGenerator的创建过程
每一个MappedStatement,都有一个非空的KeyGenerator引用。
org.apache.ibatis.mapping.MappedStatement.Builder.Builder()构造方法赋初始值源码。
org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode()覆盖KeyGenerator初始值的源码。
org.apache.ibatis.builder.xml.XMLStatementBuilder.parseSelectKeyNode()解析元素,构建SelectKeyGenerator的源码。
因此,只有SelectKeyGenerator会保存至Configuration对象的Map
Map
至此,每一个MappedStatement对象,都恰当的绑定了一个KeyGenerator对象,就可以开始工作了。
6. KeyGenerator的使用过程
keyGenerator.processBefore()方法调用时机。
org.apache.ibatis.executor.statement.BaseStatementHandler.BaseStatementHandler()构造方法源码。
即,创建StatementHandler对象时,就会执行keyGenerator.processBefore()方法。keyGenerator.processAfter()方法,自然就是Statement执行后执行了。
org.apache.ibatis.executor.statement.SimpleStatementHandler.update(Statement)方法源码。其他的StatementHandler都是类似的。
7. 批量插入,返回主键id列表
对的,你没看错,就是像上面这样for循环逐一insert操作的,此时,如果你考虑性能的话,可以使用BatchExecutor来完成,当然了,其他的Executor也是可以的。
如果文章就像上面这样写,那么就完全失去了写文章的价值,上面的for循环,谁都懂这么操作可以实现,但是,很多人想要的并不是这个例子,而是另外一种批量插入操作,返回主键id列表。那么,看第8条。
8. Mybatis批量插入,返回主键id列表为null
很多同学,包括开源中国社区,都遇到使用上面的批量insert操作,返回的主键id列表是null的问题,很多人得出结论:Mybatis不支持这种形式的批量插入并返回主键id列表。真是这样吗?
我必须明确的跟大家说,Mybatis是支持上述形式的批量插入,且可以正确返回主键id列表的。之所以返回null值,是Mybatis框架的一个bug,下一篇将具体讲述产生这个bug的原因,以及如何修复它。