一、Mybatis在插入单条数据的时候有两种方式返回自增主键:
- 对于支持生成自增主键的数据库,可以使用useGenerateKeys和keyProperty 来返回插入后的主键;
- 不支持生成自增主键的数据库,使用<selectKey>(例如oracle不支持自增主键)。
1、使用useGenerateKeys和keyProperty 来返回插入后的主键:
在insert标签中,parameterType可以是一个实体类,也可以是map类型,如下:
< insert id = “doSomething" parameterType = "map" useGeneratedKeys = "true" keyProperty = “yourId" >
...
</ insert >
或
< insert id = “doSomething" parameterType = “com.xx.yy.zz.YourClass" useGeneratedKeys = "true" keyProperty = “yourId" >
...
</ insert >
java代码:
public int doSomething(Map<String, Object> parameters);
或者
public int doSomething (YourClass c);
要在map或c中有一个字段名为yourId,Mybatis会自动把主键值赋给这个字段。
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put(“yourId”, 1234);
...
mapper.doSomething(parameters);
System.out.println(“id of the field that is primary key” + parameters.get(“yourId"));
或
YourClass c = new YourClass();
...
mapper.doSomething(c);
System.out.println(“id of the field that is primary key” + c.yourId);
2、使用selectKey返回主键:
<insert id="insertProduct-Mysql" parameterClass="com.domain.Product">
insert into PRODUCT(PRD_DESCRIPTION)
values (#description#)
<selectKey resultClass="int" keyProperty="id">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
二、批量插入返回主键:
Mybatis官网资料提供如下:
First, if your database supports auto-generated key fields (e.g. MySQL and SQL Server), then you can simply set useGeneratedKeys="true" and set the keyProperty to the target property and you're done. For example, if the Authortable above had used an auto-generated column type for the id, the statement would be modified as follows:
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
If your database also supports multi-row insert, you can pass a list or an array of Authors and retrieve the auto-generated keys.
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username, password, email, bio) values
<foreach item="item" collection="list" separator=",">
(#{item.username}, #{item.password}, #{item.email}, #{item.bio})
</foreach>
</insert>
从官网资料可以看出Mybatis是支持批量插入时返回自增主键的。
但是在本地测试的时候使用上述方式确实不能返回自增id,而且还报错(不认识keyProperty中指定的Id属性),然后在网上找相关资料。终于在Stackoverflow上面找到了一些信息。
解决办法:
- 升级Mybatis版本到3.3.1。官方在这个版本中加入了批量新增返回主键id的功能
- 在Dao中不能使用@param注解。
- Mapper.xml中使用list变量(parameterType="java.util.List")接受Dao中的参数集合。
具体看一个例子:
<!-- 批量新增 -->
<insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id" >
INSERT INTO test (relation_id, summary_id, relation_type)
VALUES
<foreach collection="list" index="index" item="shopResource" separator=",">
(#{shopResource.relationId}, #{shopResource.summaryId}, #{shopResource.relationType})
</foreach>
</insert>
public List<ShopResource> batchinsertCallId(List<ShopResource> shopResourceList){
testDao.batchInsert(shopResourceList);
return shopResourceList;//包含了id
}
参考:https://blog.csdn.net/jiangeeq/article/details/55047116
3、注意:
在用Spring管理事务时,SelectKey和插入在同一事务当中,因而Mysql这样的情况由于数据未插入到数据库中,所以是得不到自动增长的Key。取消事务管理就不会有问题。
例如:
<insert id="insert" parameterType="com.xxx.Book" useGeneratedKeys="true" keyProperty="id">
insert into t_books(name)
values (#{name,jdbcType=VARCHAR})
<selectKey resultType="java.lang.Long" order="AFTER" keyProperty="id" >
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
我使用的环境是struts2+spring3+mybatis3.2.2,很典型的三层开发架构,action,service,dao,事务控制在service层。
在action中执行以下代码,完全可获取id
bookService.insert(book);
System.out.println("book id = "+book.getId()); //打出正确的id
在service中执行以下代码,将获取不到id
bookDao.insert(book);
System.out.println("book id = "+book.getId()); //打出null
神奇吧,我不知道你们的是不是这样,总之,我这个是了。
于是开始断点调试+追踪sql执行日志,发现了端倪。当执行完 bookDao.insert(book);这行代码后,控制台打出insert 语句,
但未见select last_insert_id()执行,再执行到下一行,打印book id时,结果为null
于是继续执行到action中的下一行(println()那一行),此时见控制台,神奇的出现了select last_insert_id()。
有点明白了,应该是当事务提交时,才会去执行select last_insert_id()。因为事务在service层,只要没有离开service,事务一直有效,一旦离开service,事务立即提交。此时select last_insert_id()才会执行,并将id填充进book对象中。
这个问题造成的后果就是,在service中插入主从表关联数据时,由于不能立即获取主表id,导致插入从表数据时,不能填充主表id(有点拗口,自己体会)。
这个也就是我现在项目遇到的问题,也就是我花时间写这篇文章分享的原因。
解决办法
1.将主从表插入操作分成两个service方法,一个方法插入主表,一个方法插入从表。这样在action中,先插入主表后,就能拿到id,再传给从表。这种解决方法其实将一个事务完成的操作,拆分成两个事务完成,有数据完整性风险。
2.照样是一个service方法,完成主从表操作,但不要为此service设置事务,mybatis默认为每条执行的sql都单独开一个事务,语句执行完后,事务就提交,select last_insert_id()也会立即执行。在我这里只要将service类的@Transactional注解去掉就可以了。
3.使用存储过程。