MyBatis主键回填的两种实现方式

 更新时间:2026年04月08日 09:57:05   作者:身如柳絮随风扬  
这篇文章主要介绍了MyBatis中的主键回填机制,详细说明了其应用场景、两种实现方式的原理、配置方法、适用场景及性能对比,并通过流程图展示了执行过程,针对常见问题与注意事项进行了说明,最后提供了完整示例代码,需要的朋友可以参考下

购物下单后,订单号是怎么自动生成的?插入数据后如何直接获取自增主键?本文将带你深入理解 MyBatis 中的主键回填机制,并通过流程图和代码示例彻底掌握它。

一、业务场景:下单后如何返回订单编号?

想象一个典型的购物交易流程:

  1. 用户点击“立即购买”,填写收货信息,提交订单。
  2. 后端服务将订单数据(用户ID、商品ID、金额、状态等)插入到数据库的 orders 表中。
  3. 插入成功后,系统需要立即返回一个订单编号给前端,用于后续支付、查询等操作。

这个订单编号通常是数据库表的主键,比如自增的 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 类型。
  • orderBEFORE(在插入前执行)或 AFTER(在插入后执行)。

四、流程图:主键回填的执行过程

为了更直观地理解两种方式的执行流程,下面用流程图表示。

1.useGeneratedKeys方式流程

1.useGeneratedKeys方式流程

2.<selectKey order="AFTER">方式流程(以 MySQL 为例)

2.<selectKey order="AFTER">方式流程(以 MySQL 为例)

3.<selectKey order="BEFORE">方式流程(以 Oracle 为例)

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实现主键回填的资料请关注脚本之家其它相关文章!

相关文章

  • 图解Java ReentrantLock的条件变量Condition机制

    图解Java ReentrantLock的条件变量Condition机制

    想必大家都使用过wait()和notify()这两个方法把,他们主要用于多线程间的协同处理。而RenentrantLock也支持这样条件变量的能力,而且相对于synchronized 更加强大,能够支持多个条件变量,本文就来详细说说
    2022-10-10
  • java构建树形结构的方式及如何组装树状结构数据

    java构建树形结构的方式及如何组装树状结构数据

    这篇文章主要介绍了在Java中构建树状数据结构的几种常见方法,包括递归、使用Map/HashMap以及基于Stream流的方式,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-04-04
  • Java String类详解_动力节点Java学院整理

    Java String类详解_动力节点Java学院整理

    这篇文章主要介绍了Java String类详解,本文经多方资料的收集整理和归纳,最终撰写成文,非常不错,值得收藏,需要的的朋友参考下
    2017-04-04
  • Java中的传值与传引用实现过程解析

    Java中的传值与传引用实现过程解析

    这篇文章主要介绍了java中的传值与传引用实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Java中逆序遍历List集合的实现

    Java中逆序遍历List集合的实现

    本文主要介绍了Java中逆序遍历List集合的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • ssm框架上传图片保存到本地和数据库示例

    ssm框架上传图片保存到本地和数据库示例

    本篇文章主要介绍了ssm框架上传图片保存到本地和数据库示例,主要使用了Spring+SpringMVC+MyBatis框架集合,有兴趣的可以了解一下。
    2017-03-03
  • Java中的多种文件上传方式总结

    Java中的多种文件上传方式总结

    这篇文章主要介绍了Java中的多种文件上传方式总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Java精品项目瑞吉外卖之登陆的完善与退出功能篇

    Java精品项目瑞吉外卖之登陆的完善与退出功能篇

    这篇文章主要为大家详细介绍了java精品项目-瑞吉外卖订餐系统,此项目过大,分为多章独立讲解,本篇内容为新增菜品和分页查询功能的实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • idea项目实现移除和添加git

    idea项目实现移除和添加git

    本文指导读者如何从官网下载并安装Git,以及在IDEA中配置Git的详细步骤,首先,用户需访问Git官方网站下载适合自己操作系统的Git版本并完成安装,接着,在IDEA中通过设置找到git.exe文件以配置Gi
    2024-10-10
  • Spring IOC相关注解运用(上篇)

    Spring IOC相关注解运用(上篇)

    这篇文章主要介绍了Spring IOC相关注解的运用,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05

最新评论