KeyGenerator简介
默认情况下,insert语句并不会返回自动生成的主键,而是返回插入记录的条数。如果业务逻辑需要获取插入记录时产生的自增主键,则可以使用Mybatis提供的KeyGenerator接口。不同的数据库产品对应的主键生成策略不一样,例如,Oracle、DB2等数据库产品是通过 sequence 实现自增 id 的,在执行insert语句之前必须明确指定主键的值;而MySQL、Postgresql等数据库在执行insert 语句时,可以不指定主键,在插入过程中由数据库自动生成自增主键。KeyGenerator接口针对这些不同的数据库产品提供了对应的处理方法,KeyGenerator接口的定义如下
public interface KeyGenerator {
// 在执行insert之前执行,设置属性order="BEFORE"
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
// 在执行insert之前执行,设置属性order="AFTER"
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
Mybatis提供了三个KeyGenerator接口的实现,如下图所示
KeyGenerator用法
Jdbc3KeyGenerator
<insert id="addComment" useGeneratedKeys="true" keyProperty="id">
insert into `comment`(`blog_id`, `content`) values (#{blogId},#{content})
</insert>
useGeneratedKeys : 这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty : 指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
SelectKeyGenerator用法
mysql用法示例
<insert id="addComment">
<selectKey keyProperty="id" order="AFTER" resultType="int">
select LAST_INSERT_ID()
</selectKey>
insert into `comment`(`blog_id`, `content`) values (#{blogId},#{content})
</insert>
oracle用法示例
<insert id="addComment">
<selectKey keyProperty="id" order="BEFORE" resultType="int">
select SEQ_COMMENT.nextval from dual
</selectKey>
insert into "comment" ("id","blog_id","content") values (#{id},#{blogId},#{content})
</insert>
SelectKey最终也会被解析成一个MappedStatement,它就相当于主Sql的一个关联Sql,我们需要关注order值是AFTER,还是BEFORE,它表示关联Sql是在主Sql之前执行,还是之后执行
当然SelectKey也不局限与上述用法,比如我们使用UUID做主键
<insert id="addComment">
<selectKey keyProperty="id" order="BEFORE" resultType="string">
select UUID()
</selectKey>
insert into `comment`(`id`,`blog_id`, `content`) values (#{id},#{blogId},#{content})
</insert>
LAST_INSERT_ID
LAST_INSERT_ID()
在没有参数的情况下,LAST_INSERT_ID()返回一个BIGINT UNSIGNED(64位)值,该值表示最近执行的INSERT语句为AUTO_INCREMENT列成功插入的第一个自动生成的值。如果没有成功插入任何行,LAST_INSERT_ID()的值(默认0)将保持不变。
LAST_INSERT_ID()具有以下特点 :
作用域限制 : 生成的ID在服务器中按每个连接进行维护。这意味着函数返回给给定客户端的值是为该客户端影响AUTO_INCREMENT列的最新语句生成的第一个AUTO_INCREMENT值。此值不受其他客户端的影响,即使它们生成自己的AUTO_INCREMENT值也是如此。这种行为确保每个客户端都可以检索自己的ID,而不必关心其他客户端的活动,也不需要锁或事务。
非事务限制 : LAST_INSERT_ID()不受事务影响,即使ROLLBACK,LAST_INSERT_ID()的值不会恢复到事务之前的值
PS : 如果使用单个insert语句插入多行,LAST_INSERT_ID()将仅返回为插入的第一行生成的值。
LAST_INSERT_ID(expr)
如果将expr作为参数提供给LAST_INSERT_ID(),则函数将返回该参数的值,并将其记为LAST_INSERT_ID()返回的下一个值。用法示例如下:
源码简析
XMLStatementBuilder#parseStatementNode
通过源码我们知道,mybatis会先解析selectKey标签,如果存在selectKey标签,则使用SelectKeyGenerator。如果SqlCommand是INSERT,并且useGeneratedKeys为true,则使用Jdbc3KeyGenerator。默认使用NoKeyGenerator
processBefore执行时机
KeyGenerator的processBefore方法是在构建StatementHandler对象的时候执行的
processAfter执行时机
KeyGenerator的processAfter方法是在执行完更新操作后执行的
小结
对于Mybatis来说,SelectKeyGenerator会执行两次查询,效率上低于Jdbc3KeyGenerator