java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis实现主键回填

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

作者:身如柳絮随风扬

这篇文章主要介绍了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>

Java 接口方法

public interface OrderMapper {
    int insertOrder(Order order);
}

使用注意

方式二:<selectKey>标签(灵活,支持多种数据库)

适用场景

原理:通过子查询执行一个 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>

属性说明

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

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

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)不支持支持

选择建议

六、常见问题与注意事项

1. 为什么插入后对象的 id 还是 null?

2. 批量插入时能否使用主键回填?

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 中非常实用且高频使用的特性,它解决了插入数据后立即获取主键的痛点。掌握两种实现方式:

在实际开发中,根据数据库类型和业务需求选择合适的方式,可以大大简化代码逻辑,提升开发效率。

以上就是MyBatis主键回填的两种实现方式的详细内容,更多关于MyBatis实现主键回填的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文