Mybatis獲取自增主鍵的兩種實(shí)現(xiàn)原理小結(jié)
一、自增主鍵的獲取方式
自增主鍵是數(shù)據(jù)庫常見的主鍵生成策略(如MySQL的AUTO_INCREMENT
、Oracle 的序列等)。MyBatis針對不同數(shù)據(jù)庫的特性,提供了靈活的自增主鍵獲取方案,核心分為兩類:依賴數(shù)據(jù)庫原生自增機(jī)制的 useGeneratedKeys
方式,以及通過顯式SQL查詢獲取的 <selectKey>
方式。
1. MySQL 的兩種獲取方式
MySQL支持AUTO_INCREMENT
自增列,MyBatis 提供兩種方式獲取其生成的主鍵:
(1)useGeneratedKeys方式(推薦)
這是最簡單的方式,利用JDBC的 Statement.getGeneratedKeys()
接口直接獲取數(shù)據(jù)庫生成的主鍵,無需手動(dòng)編寫查詢邏輯。
配置示例(XML 映射)
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> INSERT INTO user (username, age) VALUES (#{username}, #{age}) </insert>
useGeneratedKeys="true"
:開啟自增主鍵獲取功能。keyProperty="id"
:指定 Java 實(shí)體類中接收主鍵的屬性(如User
類的id
字段)。
(2)<selectKey>方式
通過顯式執(zhí)行查詢SQL來獲取主鍵,MySql也支持插入后通過LAST_INSERT_ID
函數(shù)獲取剛生成的主鍵:
配置示例:
<insert id="insertUser"> <!-- order="AFTER" 表示插入后執(zhí)行查詢 --> <selectKey keyProperty="id" resultType="java.lang.Long" order="AFTER"> SELECT LAST_INSERT_ID AS id -- MySQL 專用函數(shù),返回當(dāng)前會(huì)話最后生成的自增主鍵 </selectKey> INSERT INTO user (username, age) VALUES (#{username}, #{age}) </insert>
order="AFTER"
:由于 MySQL 自增主鍵在插入后生成,因此需設(shè)置為AFTER
。SELECT LAST_INSERT_ID
:MySQL 提供的函數(shù),用于獲取當(dāng)前會(huì)話中最近一次插入生成的自增主鍵。
PS:LAST_INSERT_ID
是個(gè)函數(shù),后面要有()的,但是似乎觸發(fā)了掘金的什么feature,加上括號(hào)后會(huì)被轉(zhuǎn)成0
2. Oracle 的獲取方式
Oracle 沒有內(nèi)置的自增列機(jī)制,通常通過序列(Sequence) 生成主鍵。MyBatis 需通過 <selectKey>
標(biāo)簽先查詢序列值,再將其作為主鍵插入。
配置示例:
<insert id="insertUser"> <!-- order="BEFORE" 表示插入前先查詢序列 --> <selectKey keyProperty="id" resultType="java.lang.Long" order="BEFORE"> SELECT USER_SEQ.NEXTVAL FROM DUAL -- 查詢序列的下一個(gè)值 </selectKey> INSERT INTO user (id, username, age) VALUES (#{id}, #{username}, #{age}) </insert>
order="BEFORE"
:由于 Oracle 需先獲取序列值作為主鍵,再執(zhí)行插入,因此需設(shè)置為BEFORE
。USER_SEQ.NEXTVAL
:Oracle 序列的下一個(gè)值,作為主鍵傳入INSERT
語句。
二、源碼解析:自增主鍵的實(shí)現(xiàn)原理
MyBatis自增主鍵的核心邏輯由 KeyGenerator
接口及其實(shí)現(xiàn)類完成,結(jié)合MappedStatement
、StatementHandler
等組件,實(shí)現(xiàn)主鍵的生成、獲取與回寫。
1. 核心接口:KeyGenerator
KeyGenerator
是自增主鍵處理的核心接口,定義了主鍵生成的兩個(gè)關(guān)鍵時(shí)機(jī)(插入前 / 后):
public interface KeyGenerator { // 插入操作執(zhí)行前調(diào)用(如 Oracle 序列查詢) void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter); // 插入操作執(zhí)行后調(diào)用(如 MySQL 自增主鍵獲?。? void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter); }
MyBatis 提供兩個(gè)核心實(shí)現(xiàn)類:Jdbc3KeyGenerator
(處理 useGeneratedKeys
方式)和 SelectKeyGenerator
(處理 <selectKey>
方式)。
2.useGeneratedKeys方式的實(shí)現(xiàn)(Jdbc3KeyGenerator)
當(dāng)解析 Mapper 配置時(shí),useGeneratedKeys
等屬性會(huì)被封裝到 MappedStatement
中,并初始化 Jdbc3KeyGenerator
:
(1)創(chuàng)建PreparedStatement時(shí)設(shè)置主鍵返回標(biāo)志
執(zhí)行插入時(shí),PreparedStatementHandler
會(huì)根據(jù) MappedStatement
的配置,創(chuàng)建帶有 RETURN_GENERATED_KEYS
標(biāo)志的 PreparedStatement
,告知數(shù)據(jù)庫返回自增主鍵:
// PreparedStatementHandler.java @Override protected Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { PreparedStatement ps; String sql = boundSql.getSql(); // 若使用 Jdbc3KeyGenerator,設(shè)置 RETURN_GENERATED_KEYS 標(biāo)志 if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); } else { ps = connection.prepareStatement(sql); } // 設(shè)置超時(shí)時(shí)間等 return ps; }
(2)插入后獲取并回寫主鍵(processAfter方法)
插入執(zhí)行后,Jdbc3KeyGenerator
的 processAfter
方法被調(diào)用,通過 Statement.getGeneratedKeys()
獲取主鍵,并通過反射回寫到實(shí)體類:
@Override public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { processBatch(ms, stmt, getParameters(parameter)); } public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) { ResultSet rs = null; try { //核心:使用getGeneratedKeys方法獲取主鍵 rs = stmt.getGeneratedKeys(); final Configuration configuration = ms.getConfiguration(); final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); //獲取參數(shù)對象主鍵屬性 final String[] keyProperties = ms.getKeyProperties(); final ResultSetMetaData rsmd = rs.getMetaData(); TypeHandler<?>[] typeHandlers = null; if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) { for (Object parameter : parameters) { // there should be one row for each statement (also one for each parameter) if (!rs.next()) { break; } final MetaObject metaParam = configuration.newMetaObject(parameter); if (typeHandlers == null) { //獲取主鍵對應(yīng)的typeHandlers typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd); } //反射設(shè)置到參數(shù)中 populateKeys(rs, metaParam, keyProperties, typeHandlers); } } } //省略一些異常處理的代碼和關(guān)閉ResultSet的代碼 }
3.<selectKey>方式的實(shí)現(xiàn)(SelectKeyGenerator)
SelectKeyGenerator
實(shí)現(xiàn)了<selectKey>
,需要根據(jù)order
屬性,判斷該在processBefore
還是processAfter
中執(zhí)行:
// SelectKeyGenerator.java @Override public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { if (executeBefore) { // order="BEFORE" 時(shí)執(zhí)行 processGeneratedKeys(executor, ms, parameter); } } @Override public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { if (!executeBefore) { // order="AFTER" 時(shí)執(zhí)行 processGeneratedKeys(executor, ms, parameter); } } private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) { try { if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) { String[] keyProperties = keyStatement.getKeyProperties(); final Configuration configuration = ms.getConfiguration(); final MetaObject metaParam = configuration.newMetaObject(parameter); // 創(chuàng)建執(zhí)行器,執(zhí)行主鍵查詢操作 Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE); // 執(zhí)行查詢主鍵的操作 List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER); if (values.size() == 0) { throw new ExecutorException("SelectKey returned no data."); } else if (values.size() > 1) { throw new ExecutorException("SelectKey returned more than one value."); } else { // 創(chuàng)建 MetaObject 對象,訪問查詢主鍵的結(jié)果 MetaObject metaResult = configuration.newMetaObject(values.get(0)); // 單個(gè)主鍵 if (keyProperties.length == 1) { // 設(shè)置屬性到 metaParam 中,相當(dāng)于設(shè)置到 parameter 中 if (metaResult.hasGetter(keyProperties[0])) { setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0])); } else { setValue(metaParam, keyProperties[0], values.get(0)); } // 多個(gè)主鍵 } else { // 遍歷,進(jìn)行賦值 handleMultipleProperties(keyProperties, metaParam, metaResult); } } } } //省略一些異常處理 }
4. 調(diào)用鏈路總結(jié)
自增主鍵的處理貫穿 MyBatis 插入操作的全流程,核心鏈路如下:
- 用戶調(diào)用:sqlSession.insert("insertUser", user)
- 進(jìn)入執(zhí)行器:Executor.update(ms, parameter)(insert 本質(zhì)是 update 操作)
- 創(chuàng)建 StatementHandler:PreparedStatementHandler 根據(jù) MappedStatement 的 keyGenerator 類型,決定是否設(shè)置 RETURN_GENERATED_KEYS 標(biāo)志。
- 執(zhí)行插入:Statement.execute() 執(zhí)行 INSERT 語句。
- 主鍵處理:
- 若為 Jdbc3KeyGenerator:調(diào)用 processAfter,通過 stmt.getGeneratedKeys() 獲取主鍵并回寫。
- 若為 SelectKeyGenerator:根據(jù) order 調(diào)用 processBefore 或 processAfter,執(zhí)行 <selectKey> 中的 SQL 獲取主鍵并回寫。
三、總結(jié)
MyBatis 自增主鍵的獲取本質(zhì)是適配數(shù)據(jù)庫特性 + 封裝 JDBC 接口:
- 對于支持 getGeneratedKeys() 的數(shù)據(jù)庫(如 MySQL),優(yōu)先使用 useGeneratedKeys 方式,通過 Jdbc3KeyGenerator 直接獲取主鍵,簡潔高效。
- 對于依賴序列的數(shù)據(jù)庫(如Oracle),使用 <selectKey> 方式,通過 SelectKeyGenerator 顯式查詢主鍵,靈活兼容。 。
到此這篇關(guān)于Mybatis獲取自增主鍵的實(shí)現(xiàn)原理小結(jié)的文章就介紹到這了,更多相關(guān)Mybatis獲取自增主鍵內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決@MapperScan和@Mapper共存之坑XxxMapper?that?could?not?be?fo
這篇文章主要介紹了解決@MapperScan和@Mapper共存之坑XxxMapper?that?could?not?be?found問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06Java實(shí)現(xiàn)的計(jì)時(shí)器【秒表】功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)的計(jì)時(shí)器【秒表】功能,結(jié)合實(shí)例形式分析了Java結(jié)合JFrame框架的計(jì)時(shí)器功能相關(guān)操作技巧,需要的朋友可以參考下2019-02-02javaWeb連接數(shù)據(jù)庫實(shí)現(xiàn)簡單登陸注冊功能的全過程
初學(xué)javaWeb,老師留下一小作業(yè),用JAVA實(shí)現(xiàn)與服務(wù)器端交互,實(shí)現(xiàn)登錄和注冊功能,下面這篇文章主要給大家介紹了關(guān)于javaWeb連接數(shù)據(jù)庫實(shí)現(xiàn)簡單登陸注冊功能的相關(guān)資料,需要的朋友可以參考下2022-06-06掌握J(rèn)ava拼音轉(zhuǎn)換:pinyin4j庫使用方法及應(yīng)用價(jià)值
pinyin4j是一個(gè)開源的Java庫,用于將漢字轉(zhuǎn)換為拼音,它支持將中文字符轉(zhuǎn)換為標(biāo)準(zhǔn)的全拼形式,并能夠處理多音字和聲調(diào),本文詳細(xì)解析pinyin4j庫的核心特性、使用方法及其應(yīng)用價(jià)值,感興趣的朋友一起看看吧2025-08-08MyBatis Oracle 自增序列的實(shí)現(xiàn)方法
這篇文章給大家分享MyBatis Oracle 自增序列的實(shí)現(xiàn)方法及mybatis配置oracle的主鍵自增長的方法,非常不錯(cuò)具有一定的參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-11-11安全漏洞修復(fù)導(dǎo)致SpringBoot2.7與Springfox不兼容的偽命題解決
項(xiàng)目基于Spring Boot 2.5.2使用Springfox Swagger2生成API文檔,因安全漏洞需升級(jí)至2.7.8,導(dǎo)致兼容問題,下面就來介紹一下問題解決,感興趣的可以了解一下2025-06-06Java實(shí)現(xiàn)系統(tǒng)限流的示例代碼
限流是保障系統(tǒng)高可用的方式之一,也是大廠高頻面試題,它在微服務(wù)系統(tǒng)中,緩存、限流、熔斷是保證系統(tǒng)高可用的三板斧,所以本文我們就來聊聊如何實(shí)現(xiàn)系統(tǒng)限流吧2023-09-09Mybatis新增數(shù)據(jù)并返回主鍵id的兩種方法實(shí)現(xiàn)
本文主要介紹了Mybatis新增數(shù)據(jù)并返回主鍵id的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02