MyBatis主鍵回填的兩種實(shí)現(xiàn)方式
購(gòu)物下單后,訂單號(hào)是怎么自動(dòng)生成的?插入數(shù)據(jù)后如何直接獲取自增主鍵?本文將帶你深入理解 MyBatis 中的主鍵回填機(jī)制,并通過流程圖和代碼示例徹底掌握它。
一、業(yè)務(wù)場(chǎng)景:下單后如何返回訂單編號(hào)?
想象一個(gè)典型的購(gòu)物交易流程:
- 用戶點(diǎn)擊“立即購(gòu)買”,填寫收貨信息,提交訂單。
- 后端服務(wù)將訂單數(shù)據(jù)(用戶ID、商品ID、金額、狀態(tài)等)插入到數(shù)據(jù)庫(kù)的
orders表中。 - 插入成功后,系統(tǒng)需要立即返回一個(gè)訂單編號(hào)給前端,用于后續(xù)支付、查詢等操作。
這個(gè)訂單編號(hào)通常是數(shù)據(jù)庫(kù)表的主鍵,比如自增的 id 字段。但在執(zhí)行插入 SQL 時(shí),我們并不知道這個(gè) id 是多少 —— 它是數(shù)據(jù)庫(kù)自動(dòng)生成的。
如果采用“先插入,再查詢”的方式:
-- 第一步:插入訂單(id 自動(dòng)生成) INSERT INTO orders(user_id, amount, status) VALUES(1001, 299.00, 0); -- 第二步:根據(jù)唯一業(yè)務(wù)字段查詢剛插入的訂單 SELECT id FROM orders WHERE user_id = 1001 AND create_time = ...;
這種做法不僅繁瑣,而且在高并發(fā)下很容易出錯(cuò)(多個(gè)用戶同時(shí)下單,條件可能不唯一)。于是,主鍵回填技術(shù)應(yīng)運(yùn)而生。
二、什么是主鍵回填?
主鍵回填(Key Fillback / Key Return)是指在執(zhí)行數(shù)據(jù)庫(kù)插入操作后,自動(dòng)將生成的主鍵值填充到傳入的 Java 對(duì)象中。這樣,你無需再次查詢,就能直接通過對(duì)象的 getId() 方法拿到主鍵。
簡(jiǎn)單來說:插入時(shí)主鍵為 null,插入后 MyBatis 幫你把主鍵值“填回去”。
效果演示
Order order = new Order(); order.setUserId(1001); order.setAmount(299.00); order.setStatus(0); // 此時(shí) order.getId() == null orderMapper.insertOrder(order); // 插入后,MyBatis 自動(dòng)將生成的主鍵賦值給 order 對(duì)象 System.out.println(order.getId()); // 輸出 12345
三、主鍵回填的兩種實(shí)現(xiàn)方式
MyBatis 主要提供兩種方式實(shí)現(xiàn)主鍵回填,分別適用于不同的數(shù)據(jù)庫(kù)和場(chǎng)景。
方式一:useGeneratedKeys+keyProperty(推薦,簡(jiǎn)單高效)
適用數(shù)據(jù)庫(kù):支持自動(dòng)生成主鍵的數(shù)據(jù)庫(kù),如 MySQL、SQL Server(自增列)、PostgreSQL(serial 類型)等。
原理:MyBatis 利用 JDBC 的 Statement.getGeneratedKeys() 方法獲取數(shù)據(jù)庫(kù)自動(dòng)生成的主鍵。
配置示例:
<insert id="insertOrder" useGeneratedKeys="true" keyProperty="id">
INSERT INTO orders(user_id, amount, status)
VALUES (#{userId}, #{amount}, #{status})
</insert>useGeneratedKeys="true":開啟主鍵回填功能。keyProperty="id":指定將生成的主鍵值賦給 Java 對(duì)象的哪個(gè)屬性(對(duì)應(yīng)Order類中的id字段)。
Java 接口方法:
public interface OrderMapper {
int insertOrder(Order order);
}使用注意:
- 只能用于單條插入(批量插入時(shí)需特殊處理,不同數(shù)據(jù)庫(kù)支持情況不一)。
- 數(shù)據(jù)庫(kù)表必須定義自增列或類似機(jī)制。
方式二:<selectKey>標(biāo)簽(靈活,支持多種數(shù)據(jù)庫(kù))
適用場(chǎng)景:
- 數(shù)據(jù)庫(kù)沒有自增列(如 Oracle 使用序列)。
- 需要在插入前生成主鍵(比如使用 UUID)。
- 需要執(zhí)行自定義查詢來獲取主鍵值(如 MySQL 的
LAST_INSERT_ID())。
原理:通過子查詢執(zhí)行一個(gè) SQL 語(yǔ)句來獲取主鍵,可以指定在插入語(yǔ)句之前(order="BEFORE")或之后(order="AFTER")執(zhí)行。
示例 1:MySQL 插入后獲取自增 ID
<insert id="insertOrder">
<!-- 插入后執(zhí)行,獲取最后插入的 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:目標(biāo)對(duì)象的屬性名。resultType:主鍵的 Java 類型。order:BEFORE(在插入前執(zhí)行)或AFTER(在插入后執(zhí)行)。
四、流程圖:主鍵回填的執(zhí)行過程
為了更直觀地理解兩種方式的執(zhí)行流程,下面用流程圖表示。
1.useGeneratedKeys方式流程

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

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

五、兩種方式的對(duì)比與選擇
| 對(duì)比項(xiàng) | useGeneratedKeys | <selectKey> |
|---|---|---|
| 配置復(fù)雜度 | 極低,兩個(gè)屬性即可 | 稍高,需要寫子查詢 |
| 適用數(shù)據(jù)庫(kù) | 支持自增列的數(shù)據(jù)庫(kù)(MySQL 等) | 所有數(shù)據(jù)庫(kù)(Oracle、SQL Server 等) |
| 性能 | 高,一次網(wǎng)絡(luò)交互 | 可能兩次交互(AFTER 時(shí)) |
| 批量插入支持 | 有限制(需看驅(qū)動(dòng)) | 一般不用于批量 |
| 主鍵生成時(shí)機(jī) | 插入后 | 可自由控制(BEFORE / AFTER) |
| 非數(shù)值主鍵(UUID) | 不支持 | 支持 |
選擇建議:
- MySQL + 自增主鍵 → 優(yōu)先使用
useGeneratedKeys。 - Oracle 序列 / UUID / 復(fù)合主鍵 → 使用
<selectKey>。 - 需要插入前生成主鍵(如分布式 ID) → 使用
<selectKey order="BEFORE">。
六、常見問題與注意事項(xiàng)
1. 為什么插入后對(duì)象的 id 還是 null?
- 檢查
useGeneratedKeys="true"和keyProperty是否正確配置。 - 檢查
keyProperty對(duì)應(yīng)的屬性在 Java 對(duì)象中是否有 setter 方法(MyBatis 通過反射賦值)。 - 確認(rèn)數(shù)據(jù)庫(kù)表確實(shí)支持自增列(MySQL 需
AUTO_INCREMENT)。
2. 批量插入時(shí)能否使用主鍵回填?
- MySQL 驅(qū)動(dòng)支持批量插入時(shí)返回生成的主鍵,但 MyBatis 的
useGeneratedKeys對(duì)批量支持不統(tǒng)一。建議使用循環(huán)單條插入,或者使用<foreach>配合特殊寫法(需要數(shù)據(jù)庫(kù)驅(qū)動(dòng)支持)。 - 簡(jiǎn)單場(chǎng)景下,可以放棄批量回填,插入后單獨(dú)查詢。
3.LAST_INSERT_ID()與并發(fā)
MySQL 的 LAST_INSERT_ID() 是連接級(jí)別的,每個(gè)客戶端連接返回自己最后插入的 ID,不會(huì)受其他并發(fā)連接影響,因此是安全的。
4. 主鍵回填后,對(duì)象會(huì)被修改嗎?
會(huì)!MyBatis 會(huì)直接修改傳入的 Java 對(duì)象的屬性值。因此你無需重新接收返回值,直接使用原對(duì)象即可。
七、完整示例代碼
數(shù)據(jù)庫(kù)表(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 實(shí)體類
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>測(cè)試代碼
@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("影響行數(shù):" + rows);
System.out.println("回填后的主鍵:" + order.getId()); // 輸出自增 ID
}
}八、總結(jié)
主鍵回填是 MyBatis 中非常實(shí)用且高頻使用的特性,它解決了插入數(shù)據(jù)后立即獲取主鍵的痛點(diǎn)。掌握兩種實(shí)現(xiàn)方式:
useGeneratedKeys:簡(jiǎn)潔高效,MySQL 首選。<selectKey>:靈活強(qiáng)大,適配各種數(shù)據(jù)庫(kù)及非自增主鍵場(chǎng)景。
在實(shí)際開發(fā)中,根據(jù)數(shù)據(jù)庫(kù)類型和業(yè)務(wù)需求選擇合適的方式,可以大大簡(jiǎn)化代碼邏輯,提升開發(fā)效率。
以上就是MyBatis主鍵回填的兩種實(shí)現(xiàn)方式的詳細(xì)內(nèi)容,更多關(guān)于MyBatis實(shí)現(xiàn)主鍵回填的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
設(shè)計(jì)模式之模版方法模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了設(shè)計(jì)模式之模版方法模式,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
Java開發(fā)實(shí)現(xiàn)人機(jī)猜拳游戲
這篇文章主要為大家詳細(xì)介紹了Java開發(fā)實(shí)現(xiàn)人機(jī)猜拳游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
MyBatis通用Mapper和PageHelper的過程詳解
這篇文章主要介紹了MyBatis通用Mapper和PageHelper的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
java利用反射實(shí)現(xiàn)動(dòng)態(tài)代理示例
這篇文章主要介紹了java利用反射實(shí)現(xiàn)動(dòng)態(tài)代理示例,需要的朋友可以參考下2014-04-04
使用spring mail發(fā)送html郵件的示例代碼
本篇文章主要介紹了使用spring mail發(fā)送html郵件的示例代碼,這里整理了詳細(xì)的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09
迪米特法則_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了迪米特法則,迪米特法則就是一個(gè)在類創(chuàng)建方法和屬性時(shí)需要遵守的法則,有興趣的可以了解一下2017-08-08

