Mybatis insert时返回自增主键

本篇文章对MyBatis操作MySQL时自增主键返回情况进行详细的实验,给出不同情况下Mybatis返回自增主键的不同行为,仅基于实验结果,不做源码分析。

1. 实验对比维度
(1)单纯的insert和insert on duplicate key update
这也是本文最大的特点,查询网上各种阐述Mybatis返回主键的文章,基本只关注insert时Mybatis返回主键的情况,对于插入或更新的sql语句insert on duplicate key update时mybatis返回主键(此时还细分为仅insert,仅update和insert和update混合三种情况)的文章则比较少。

(2)selectKey和useGeneratedKeys
Mybatis用这两个方法都能返回主键,前者一般用于单个插入,后者一般用于批量插入。本文还会给出两者更加细致的实验区别。

(3)@Param和parameterType
Mybatis传参时可以用@Param注解或者在xml中用parmaterType引用java对象,本文会实验两者传参对返回主键的影响。

(4)单个和批量
单个插入、批量插入、单个插入或更新和批量插入或更新时Mybatis返回主键情况各不相同,本文针对此进行实验分析。

(5)keyProperty写法
这个维度和@Param、parameterType紧密结合。不管采用selectKey还是useGeneratedKeys,返回主键时都需要用keyProperty指定主键对应的Java对象属性名,以便将主键设置到Java对象上(达到返回主键的目的)。而@Param和parameterType影响入参名字,也就会影响keyProperty对应的属性名写法,进一步会影响到返回主键的行为。

2. 基本概念介绍
对本文感兴趣的人一般已经熟悉MySQL和Mybatis,在工程中也有大量应用,但是为了帮助读者更顺畅的阅读,笔者还是介绍下本文涉及的相关基本概念。

(1)插入或更新SQL(简称InsertOrUpdate)
当我们往数据库插入记录时,如果数据库原先不存在该记录,那么就正常插入(此时就是insert);如果数据库原先存在该记录,那么就更新此记录(此时就是update),用一条SQL语句完成上述要求就是所谓的InsertOrUpdate。

MySQL判断记录是否存在的依据是主键或者唯一索引,insert在主键或者唯一索引已经存在的情况下会插入失败,而InsertOrUpdate在主键或者唯一索引已经存在的情况下就变成了根据主键或唯一索引update的操作。

有人会问原先有多个记录同时与插入的记录相同,会发生什么?答案是MySQL会更新第一个匹配的记录,其余的则不更新(网上看到的,笔者没有实验过),本质上这是由于单表中有多个唯一索引并存,所以有人会推荐一张表最好只建一个唯一索引。

MySQL实现InsertOrUpdate的语句有两种:一种就是本文要实验的Insert into values on duplicate key update语句。另一种是replace into values语句。insert on duplicate key update在发现记录已经存在时就地更新,或者说和update行为一致。replace into在发现记录已经存在时,先把原先的记录删除,然后再插入新的记录,相当于delete+insert操作。两者具体的细节可以参考下其他文章。

这里说明下为什么选取insert on duplicate key on而非replace into做实验:目前后端开发的数据表设计,比较流行用自增主键而不是自己选定字段做主键(这样做有诸多好处,可以参考MySQL的底层数据结构),并且也不依赖数据库提供的外键功能,而是在程序逻辑中保证数据一致性关系。这样一来replace into的功能就非常坑,它在记录已经存在时会改变主键,带来数据不一致的风险。实战中笔者建议除非特殊需求否则不要用replace into,即便当下没问题,将来扩展起来也容易埋坑。即便insert on duplicate key能真正做到Insert和Update一体,笔者基本也只用它来做批量更新用,不会使用其不存在插入、存在则更新的特性——宁愿在程序逻辑里先查一遍,区分需要insert和需要update的记录,该insert的insert,该update的update——因为MySQL的语言和Java等命令型语言不一样,它是有解释器的,开发者输入的SQL语句具体怎么执行不是由开发者决定的而是由MySQL决定的,所以功能越多的、语句越复杂的SQL执行的结果越不可控,也很难调试和维护。比如InsertOrUpdate在遇到多个相同的记录时只会更新第一个相同的记录就是不可控性的表现,非常坑,但是如果在应用程序中处理这些逻辑就很方便、很确定。

无论是insert on duplicate key update还是replace into,在插入时MySQL返回的影响行数就是插入的记录数,但是在更新时返回的影响行数时更新行数*2。所以笔者只会在批量更新时放心的使用insert on duplicate key update,这样根据返回值是否是参数大小的2倍就能判断是否只进行了更新操作。

笔者推崇开发中把数据库当作单纯的数据存储服务,减少其业务逻辑的负担,返璞归真。数据库语句应该尽量单表、单crud操作,不要动则几十上百行SQL,这样也符合当前微服务的趋势。笔者刚入行时经常听闻某某大神把几分钟的SQL优化到几秒,非常佩服,后来才发现本质上这是个比烂大赛,开发人员偷懒用重SQL语句弥补开发效率,一个联表查询就能从地球联到火星,留下了一大堆低效、无扩展性、难阅读、无从调试的代码,只能借助于SQL大神擦屁股,这种屁股擦起来其实对人也没啥长进(求职时看到职责描述有“能书写复杂SQL或者优化复杂SQL”的,亲们最好留个心眼...)。还是要做好系统架构、引导好团队的开发习惯、合理设计表和索引、细致负责的规划梳理业务,避免出现SQL大神才好。

题外话说的太多了,既然本文是Mybatis返回主键的实验,replace into这种会改变主键而且实战中也不建议使用的语句当然不予考虑。

(2)selectKey和useGeneratedKeys的异同
关于两者介绍的文章也很多。概括来说selectKey用于单个记录返回主键,useGeneratedKeys单记录多记录都能返回主键。所以单记录插入时实验会对比selectKey和useGeneratedKeys,而批量插入时只会采用useGeneratedKeys。

另外useGeneratedKeys=true需要数据库的支持,mysql可以使用,但是oracle不支持。selectKey实用的last_insert_id()的适应范围更广。

这里根据后面的实验先提前下个结论:在数据库兼容的情况下,返回主键的方式用useGeneratedKeys是最佳实践,selectKey在某些情况下(单记录)不会返回主键。

(3)@Param和parameterType的异同
基本异同也请参考其他文章。简单来说有个数据库映射实体Boy,其属性id映射数据库自增主键。如果dao中入参用@Param("entity")标注,那么在Mybatis的映射xml文件里,引用boy.id要写成#{entity.id}。如果在映射xml文件用parameterType="全路径名.Boy",那么引用boy.id只用写成#{id}。

@Param在替换dao的入参实体时可能免去修改映射文件工作,而且多参数情况下只能用@Param。parameterType会让映射文件中引用对象属性写法变得简单,但是只能适应单参数情况。根据后面的实验结果,这里提前给结论:在需要返回主键场景(插入、批量插入、插入或更新,批量插入或更新)里,parameterType比@Param适应性更好,@param在某些场景下不会返回主键。

参考转载链接:https://blog.youkuaiyun.com/qq_27680317/article/details/81118070
 

<think>我们正在处理用户关于MyBatis插入后返回主键的问题。根据引用[1][2][3],我们可以看到在MyBatis中有两种主要方式来实现这个需求:使用`@SelectKey`注解(注解方式)或使用`<selectKey>`标签(XML方式)。两种方式的核心都是执行一个数据库函数来获取最后插入的自ID。 用户的需求是:在执行新语句后返回主键的值。 根据引用内容,我们可以总结如下: 1. 对于MySQL数据库,常用的函数是`LAST_INSERT_ID()`(引用[1][2])或`@@IDENTITY`(引用[3])。 2. 使用`<selectKey>`标签,需要设置`order`属性为`AFTER`(表示在insert语句之后执行),`keyProperty`指定将返回的值设置到参数的哪个属性中,`resultType`指定返回值的类型。 3. 在注解方式中,使用`@SelectKey`注解,并设置`before=false`表示在insert之后执行。 因此,我们可以提供两种方式的示例: 方式一:XML映射文件方式 ```xml <insert id="insertUser" parameterType="User"> <!-- 在插入之后,获取自主键并设置到参数的id属性中 --> <selectKey keyProperty="id" resultType="long" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO user (username, password) VALUES (#{username}, #{password}) </insert> ``` 方式二:注解方式(在Mapper接口的方法上使用) ```java @Insert("INSERT INTO user (username, password) VALUES (#{username}, #{password})") @SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = long.class) int insertUser(User user); ``` 注意:在调用insert方法后,传入的参数对象(例如User对象)的id属性就会被自动赋值为自主键的值。 另外,引用[3]中使用了`SELECT @@IDENTITY`,但需要注意,`@@IDENTITY`和`LAST_INSERT_ID()`在MySQL中都可以获取自ID,但`LAST_INSERT_ID()`是基于当前连接的,不会受到其他连接的影响,更安全。而`@@IDENTITY`是获取当前数据库最后一次插入的自ID,如果有其他连接插入了数据,可能会获取到错误的ID。所以推荐使用`LAST_INSERT_ID()`。 因此,我们给出推荐做法:使用`LAST_INSERT_ID()`。 引用[1][2][3]都提供了类似的方法,我们可以参考。 最后,我们还需要注意,不同的数据库获取自主键的方式可能不同,例如Oracle需要使用序列。但用户明确提到MySQL,所以这里只考虑MySQL。 根据以上分析,我们给出回答。</think>### MyBatis 插入操作后返回主键值的方法 在 MyBatis 中获取插入操作后的自主键值,主要通过 `<selectKey>` 标签或 `@SelectKey` 注解实现。以下是两种常用方法: #### 方法一:XML 映射文件配置(推荐) ```xml <insert id="insertUser" parameterType="User"> <!-- 核心配置:插入后获取自ID --> <selectKey keyProperty="id" <!-- 将返回值绑定到实体类的id属性 --> order="AFTER" <!-- 在INSERT语句执行后执行 --> resultType="java.lang.Long" statementType="PREPARED"> SELECT LAST_INSERT_ID() <!-- MySQL函数获取最新自ID --> </selectKey> INSERT INTO user (name, email) VALUES (#{name}, #{email}) </insert> ``` **调用方式**: ```java User user = new User("张三", "zhangsan@example.com"); mapper.insertUser(user); // 插入后自动填充主键 System.out.println("生成的主键ID:" + user.getId()); // 直接获取实体对象的id ``` #### 方法二:注解方式配置 ```java @Insert("INSERT INTO user (name, email) VALUES (#{name}, #{email})") @SelectKey( statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", // 绑定到实体类的id属性 before = false, // 在INSERT之后执行 resultType = Long.class ) int insertUser(User user); ``` #### 关键配置说明: 1. **`keyProperty`** 指定实体类中接收主键值的属性名(如 `id`) 2. **`order`** - `AFTER`:适用于 MySQL 自主键(插入后获取) - `BEFORE`:适用于 Oracle 序列(插入前获取) 3. **数据库函数** - MySQL:`SELECT LAST_INSERT_ID()` (确保同连接内立即获取,不受其他会话影响[^2][^3]) - SQL Server:`SELECT SCOPE_IDENTITY()` - PostgreSQL:`RETURNING id` > ⚠️ **注意事项** > - 需确保数据库表主键设置为 `AUTO_INCREMENT`(MySQL) > - 批量插入 `LAST_INSERT_ID()` 仅返回第一条数据的ID > - 高并发场景建议使用 `@@IDENTITY`(但需注意触发器影响)[^3] --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值