购物下单后,订单号是怎么自动生成的?插入数据后如何直接获取自增主键?本文将带你深入理解 mybatis 中的主键回填机制,并通过流程图和代码示例彻底掌握它。
一、业务场景:下单后如何返回订单编号?
想象一个典型的购物交易流程:
- 用户点击“立即购买”,填写收货信息,提交订单。
- 后端服务将订单数据(用户id、商品id、金额、状态等)插入到数据库的
orders表中。 - 插入成功后,系统需要立即返回一个订单编号给前端,用于后续支付、查询等操作。
这个订单编号通常是数据库表的主键,比如自增的 id 字段。但在执行插入 sql 时,我们并不知道这个 id 是多少 —— 它是数据库自动生成的。
如果采用“先插入,再查询”的方式:
-- 第一步:插入订单(id 自动生成) insert into orders(user_id, amount, status) values(1001, 299.00, 0); -- 第二步:根据唯一业务字段查询刚插入的订单 select id from orders where user_id = 1001 and create_time = ...;
这种做法不仅繁琐,而且在高并发下很容易出错(多个用户同时下单,条件可能不唯一)。于是,主键回填技术应运而生。
二、什么是主键回填?
主键回填(key fillback / key return)是指在执行数据库插入操作后,自动将生成的主键值填充到传入的 java 对象中。这样,你无需再次查询,就能直接通过对象的 getid() 方法拿到主键。
简单来说:插入时主键为 null,插入后 mybatis 帮你把主键值“填回去”。
效果演示
order order = new order(); order.setuserid(1001); order.setamount(299.00); order.setstatus(0); // 此时 order.getid() == null ordermapper.insertorder(order); // 插入后,mybatis 自动将生成的主键赋值给 order 对象 system.out.println(order.getid()); // 输出 12345
三、主键回填的两种实现方式
mybatis 主要提供两种方式实现主键回填,分别适用于不同的数据库和场景。
方式一:usegeneratedkeys+keyproperty(推荐,简单高效)
适用数据库:支持自动生成主键的数据库,如 mysql、sql server(自增列)、postgresql(serial 类型)等。
原理:mybatis 利用 jdbc 的 statement.getgeneratedkeys() 方法获取数据库自动生成的主键。
配置示例:
<insert id="insertorder" usegeneratedkeys="true" keyproperty="id">
insert into orders(user_id, amount, status)
values (#{userid}, #{amount}, #{status})
</insert>usegeneratedkeys="true":开启主键回填功能。keyproperty="id":指定将生成的主键值赋给 java 对象的哪个属性(对应order类中的id字段)。
java 接口方法:
public interface ordermapper {
int insertorder(order order);
}使用注意:
- 只能用于单条插入(批量插入时需特殊处理,不同数据库支持情况不一)。
- 数据库表必须定义自增列或类似机制。
方式二:<selectkey>标签(灵活,支持多种数据库)
适用场景:
- 数据库没有自增列(如 oracle 使用序列)。
- 需要在插入前生成主键(比如使用 uuid)。
- 需要执行自定义查询来获取主键值(如 mysql 的
last_insert_id())。
原理:通过子查询执行一个 sql 语句来获取主键,可以指定在插入语句之前(order="before")或之后(order="after")执行。
示例 1:mysql 插入后获取自增 id
<insert id="insertorder">
<!-- 插入后执行,获取最后插入的 id -->
<selectkey keyproperty="id" resulttype="int" order="after">
select last_insert_id()
</selectkey>
insert into orders(user_id, amount, status)
values (#{userid}, #{amount}, #{status})
</insert>示例 2:oracle 插入前从序列获取 id
<insert id="insertorder">
<selectkey keyproperty="id" resulttype="long" order="before">
select orders_seq.nextval from dual
</selectkey>
insert into orders(id, user_id, amount, status)
values (#{id}, #{userid}, #{amount}, #{status})
</insert>示例 3:使用 uuid 作为主键(插入前生成)
<insert id="insertuser">
<selectkey keyproperty="id" resulttype="string" order="before">
select replace(uuid(), '-', '')
</selectkey>
insert into users(id, name) values (#{id}, #{name})
</insert>属性说明:
keyproperty:目标对象的属性名。resulttype:主键的 java 类型。order:before(在插入前执行)或after(在插入后执行)。
四、流程图:主键回填的执行过程
为了更直观地理解两种方式的执行流程,下面用流程图表示。
1.usegeneratedkeys方式流程

2.<selectkey order="after">方式流程(以 mysql 为例)

3.<selectkey order="before">方式流程(以 oracle 为例)

五、两种方式的对比与选择
| 对比项 | usegeneratedkeys | <selectkey> |
|---|---|---|
| 配置复杂度 | 极低,两个属性即可 | 稍高,需要写子查询 |
| 适用数据库 | 支持自增列的数据库(mysql 等) | 所有数据库(oracle、sql server 等) |
| 性能 | 高,一次网络交互 | 可能两次交互(after 时) |
| 批量插入支持 | 有限制(需看驱动) | 一般不用于批量 |
| 主键生成时机 | 插入后 | 可自由控制(before / after) |
| 非数值主键(uuid) | 不支持 | 支持 |
选择建议:
- mysql + 自增主键 → 优先使用
usegeneratedkeys。 - oracle 序列 / uuid / 复合主键 → 使用
<selectkey>。 - 需要插入前生成主键(如分布式 id) → 使用
<selectkey order="before">。
六、常见问题与注意事项
1. 为什么插入后对象的 id 还是 null?
- 检查
usegeneratedkeys="true"和keyproperty是否正确配置。 - 检查
keyproperty对应的属性在 java 对象中是否有 setter 方法(mybatis 通过反射赋值)。 - 确认数据库表确实支持自增列(mysql 需
auto_increment)。
2. 批量插入时能否使用主键回填?
- mysql 驱动支持批量插入时返回生成的主键,但 mybatis 的
usegeneratedkeys对批量支持不统一。建议使用循环单条插入,或者使用<foreach>配合特殊写法(需要数据库驱动支持)。 - 简单场景下,可以放弃批量回填,插入后单独查询。
3.last_insert_id()与并发
mysql 的 last_insert_id() 是连接级别的,每个客户端连接返回自己最后插入的 id,不会受其他并发连接影响,因此是安全的。
4. 主键回填后,对象会被修改吗?
会!mybatis 会直接修改传入的 java 对象的属性值。因此你无需重新接收返回值,直接使用原对象即可。
七、完整示例代码
数据库表(mysql)
create table `orders` ( `id` int(11) not null auto_increment, `user_id` int(11) not null, `amount` decimal(10,2) not null, `status` tinyint(4) default '0', primary key (`id`) );
java 实体类
public class order {
private integer id;
private integer userid;
private bigdecimal amount;
private integer status;
// 省略 getter / setter / tostring
}mapper 接口
@mapper
public interface ordermapper {
// 方式一:usegeneratedkeys
int insertorder(order order);
// 方式二:selectkey (mysql after)
int insertorderwithselectkey(order order);
}xml 映射文件
<!-- 方式一 -->
<insert id="insertorder" usegeneratedkeys="true" keyproperty="id">
insert into orders(user_id, amount, status)
values (#{userid}, #{amount}, #{status})
</insert>
<!-- 方式二 -->
<insert id="insertorderwithselectkey">
<selectkey keyproperty="id" resulttype="int" order="after">
select last_insert_id()
</selectkey>
insert into orders(user_id, amount, status)
values (#{userid}, #{amount}, #{status})
</insert>测试代码
@springboottest
class ordermappertest {
@autowired
private ordermapper ordermapper;
@test
void testinsert() {
order order = new order();
order.setuserid(1001);
order.setamount(new bigdecimal("199.00"));
order.setstatus(0);
int rows = ordermapper.insertorder(order);
system.out.println("影响行数:" + rows);
system.out.println("回填后的主键:" + order.getid()); // 输出自增 id
}
}八、总结
主键回填是 mybatis 中非常实用且高频使用的特性,它解决了插入数据后立即获取主键的痛点。掌握两种实现方式:
usegeneratedkeys:简洁高效,mysql 首选。<selectkey>:灵活强大,适配各种数据库及非自增主键场景。
在实际开发中,根据数据库类型和业务需求选择合适的方式,可以大大简化代码逻辑,提升开发效率。
以上就是mybatis主键回填的两种实现方式的详细内容,更多关于mybatis实现主键回填的资料请关注代码网其它相关文章!
发表评论