面試官:MyBatis中ResultMap的實現(xiàn)原理是什么?
嗨,你好呀,我是哪吒。
面試的時候,被問到 “MyBatis中resultMap的實現(xiàn)原理是什么?”
我的第一反應(yīng)就是,resultMap不就是進(jìn)行自動映射的嘛!還有原理?
MyBatis支持自動映射,可以根據(jù)查詢結(jié)果的列名和Java對象的屬性名自動匹配。在使用自動映射時,結(jié)果集中的列名會與Java對象的屬性名進(jìn)行匹配,無需在Mapper XML文件中手動配置映射關(guān)系,簡化了開發(fā)。
通過標(biāo)簽配置查詢結(jié)果集與Java對象之間的映射關(guān)系,或者使用指定查詢結(jié)果映射到的Java對象類型。在insert、update、delete等操作中,也會使用和標(biāo)簽配置參數(shù)與SQL語句中的占位符之間的映射關(guān)系。
通過TypeHandler實現(xiàn)Java類型和數(shù)據(jù)庫字段類型之間的轉(zhuǎn)換。MyBatis提供了一些內(nèi)置的TypeHandler,同時也允許用戶自定義TypeHandler來處理特定的轉(zhuǎn)換邏輯,確保數(shù)據(jù)庫字段的數(shù)據(jù)類型正確地映射到Java對象的屬性類型。
MyBatis的TypeHandler是一個接口,用于處理Java類型與JDBC類型之間的轉(zhuǎn)換。
當(dāng)執(zhí)行SQL語句時,如果語句中包含了參數(shù)占位符(如#{param}),MyBatis會使用TypeHandler將Java方法參數(shù)的類型轉(zhuǎn)換為JDBC可以理解的SQL類型,以便在數(shù)據(jù)庫操作中使用。
查詢數(shù)據(jù)庫后,MyBatis會使用TypeHandler將結(jié)果集中的數(shù)據(jù)從SQL類型轉(zhuǎn)換為Java對象的屬性類型,這樣就能夠?qū)⒉樵兘Y(jié)果映射到Java對象上。
小結(jié)一下:
TypeHandler主要用于處理單個屬性的映射,而resultMap則用于處理整個結(jié)果集的映射。
TypeHandler 是用于處理單個Java對象屬性與數(shù)據(jù)庫字段之間的映射。它負(fù)責(zé)將Java對象的屬性值轉(zhuǎn)換為JDBC可以理解的類型,以及將查詢結(jié)果從JDBC類型轉(zhuǎn)換回Java對象的屬性類型。這種轉(zhuǎn)換是基于Java類型和JDBC類型之間的映射關(guān)系來實現(xiàn)的。
resultMap 是用來定義如何將整個查詢結(jié)果集映射到Java對象。它提供了更復(fù)雜的映射能力,允許開發(fā)者自定義如何將數(shù)據(jù)庫列名映射到Java對象的屬性上,適用于復(fù)雜的數(shù)據(jù)結(jié)構(gòu),如關(guān)聯(lián)查詢的結(jié)果。
再分享幾道關(guān)于MyBatis的常見問題。
1.當(dāng)查詢結(jié)果集包含多個表的聯(lián)合查詢時,如何使用resultMap將這些結(jié)果映射到Java對象?
在Mapper XML文件中定義resultMap,將查詢結(jié)果映射到Java對象上??梢允褂迷貋硖幚黻P(guān)聯(lián)查詢的結(jié)果,使用元素來處理集合類型的屬性。
<resultMap id="userOrderResult" type="com.example.UserOrder">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
<collection property="orders" ofType="com.example.Order">
<id property="orderId" column="order_id"/>
<result property="productName" column="product_name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
在執(zhí)行查詢操作時,引用這個resultMap
<select id="getUserOrders" resultMap="userOrderResult">
SELECT u.id as user_id, u.username, u.email, o.id as order_id, o.product_name, o.price
FROM user u
LEFT JOIN order o ON u.id = o.user_id
WHERE u.id = #{userId}
</select>
這樣,MyBatis就會根據(jù)定義的resultMap將查詢結(jié)果映射到Java對象上,并將這些對象返回給調(diào)用者。
2.如何使用MyBatis的resultMap進(jìn)行分頁查詢?
在Mapper XML文件中定義resultMap,將查詢結(jié)果映射到Java對象上。
<resultMap id="userResult" type="com.example.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
</resultMap>
在執(zhí)行查詢操作時,引用這個resultMap。
<select id="getUsersByPage" resultMap="userResult">
SELECT * FROM user LIMIT #{offset}, #{pageSize}
</select>
其中,#{offset}表示偏移量,即從第幾條記錄開始查詢;#{pageSize}表示每頁顯示的記錄數(shù)。
最后,在調(diào)用該查詢方法時,傳入相應(yīng)的參數(shù)即可實現(xiàn)分頁查詢。例如:
List<User> users = userMapper.getUsersByPage(0, 10); // 獲取前10條記錄
3.resultMap是如何提高整體性能的?
(1)可重用性
定義好的可以在多個查詢語句中重復(fù)使用,提高了代碼的復(fù)用性和維護(hù)性。而且,MyBatis還提供了繼承的功能,進(jìn)一步增加了配置的靈活性,使配置的重復(fù)利用更加便捷。
(2)二級緩存
可以幫助 MyBatis 識別并利用二級緩存(如與數(shù)據(jù)庫查詢結(jié)果的緩存),從而提高系統(tǒng)性能,減少對數(shù)據(jù)庫的頻繁訪問。
(3)減少字段映射錯誤
通過明確定義,開發(fā)人員可以準(zhǔn)確地指定查詢結(jié)果集中的每列數(shù)據(jù)應(yīng)該映射到哪個 Java 對象的屬性上。這有助于避免因為字段名字或順序變化而導(dǎo)致的映射錯誤,從而減少調(diào)試和排查錯誤的時間,提高開發(fā)效率。
4.MyBatis是如何實現(xiàn)PreparedStatement的?
MyBatis通過使用JDBC的PreparedStatement來實現(xiàn)預(yù)編譯的SQL語句。
- 獲取數(shù)據(jù)庫連接:MyBatis首先需要獲取一個數(shù)據(jù)庫連接對象;
- 準(zhǔn)備SQL語句:MyBatis會將用戶傳入的參數(shù)(如#{xxx})與SQL語句中的占位符(即"?")進(jìn)行綁定。這個過程稱為預(yù)編譯,它允許MyBatis將參數(shù)安全地插入到SQL語句中,防止了SQL注入攻擊。
- 創(chuàng)建PreparedStatement:MyBatis在執(zhí)行SQL語句時,會為每次查詢創(chuàng)建一個新的PreparedStatement對象。這個對象是JDBC API的一部分,它表示一種預(yù)編譯的SQL語句,可以提高執(zhí)行效率和安全性。
- 執(zhí)行SQL語句:MyBatis使用PreparedStatement對象的executeQuery方法來執(zhí)行SQL查詢,并返回結(jié)果集。
- 處理結(jié)果集:MyBatis將結(jié)果集轉(zhuǎn)換為Java對象,這個過程可以通過配置的resultMap來完成,它可以將數(shù)據(jù)庫的列映射到Java對象的屬性上。
- 資源釋放:執(zhí)行完畢后,MyBatis會負(fù)責(zé)關(guān)閉PreparedStatement和Connection等資源,以釋放數(shù)據(jù)庫連接。
5.MyBatis如何關(guān)閉數(shù)據(jù)庫連接?
(1)連接池
MyBatis通常與連接池一起使用,連接池負(fù)責(zé)管理數(shù)據(jù)庫連接的創(chuàng)建、分配和釋放。當(dāng)MyBatis需要執(zhí)行SQL語句時,它會從連接池中獲取一個可用的連接,而不是直接創(chuàng)建新的連接。
(2)事務(wù)管理
MyBatis提供了事務(wù)管理的功能,它會根據(jù)配置的事務(wù)策略(如提交或回滾)來處理數(shù)據(jù)庫連接的關(guān)閉。如果事務(wù)提交成功,連接會被返回到連接池中;如果事務(wù)回滾,連接可能會被關(guān)閉。
(3)資源清理
MyBatis在執(zhí)行SQL語句后,會負(fù)責(zé)關(guān)閉PreparedStatement和ResultSet等資源,以釋放數(shù)據(jù)庫連接。這可以通過配置的資源清理策略來實現(xiàn),例如使用try-with-resources語句或顯式調(diào)用close方法。
(4)配置參數(shù)
MyBatis的配置參數(shù)也會影響數(shù)據(jù)庫連接的關(guān)閉行為。例如,可以設(shè)置是否自動提交事務(wù)、是否緩存查詢結(jié)果等,這些都會影響連接的關(guān)閉時機(jī)和方式。
(5)異常處理
MyBatis在執(zhí)行過程中可能會遇到各種異常,如SQL錯誤、網(wǎng)絡(luò)中斷等。在這些情況下,MyBatis會根據(jù)異常類型來決定如何處理數(shù)據(jù)庫連接,例如重試、回滾或關(guān)閉連接。
(6)插件機(jī)制
MyBatis還支持插件機(jī)制,允許開發(fā)者自定義插件來攔截和處理數(shù)據(jù)庫操作。通過插件,可以實現(xiàn)更復(fù)雜的資源管理和異常處理邏輯,例如自定義連接池、監(jiān)控數(shù)據(jù)庫性能等。
6.MyBatis如何管理連接池的?
MyBatis作為一個ORM框架,它本身并不直接管理數(shù)據(jù)庫連接,而是通過配置數(shù)據(jù)源來實現(xiàn)。
(1)內(nèi)置連接池
當(dāng)在MyBatis配置文件中設(shè)置時,MyBatis會使用內(nèi)置的連接池。這個連接池是在MyBatis內(nèi)部實現(xiàn)的,它會在啟動時初始化一定數(shù)量的數(shù)據(jù)庫連接,并在需要時提供給MyBatis使用。
(2)第三方連接池
如果需要使用第三方的數(shù)據(jù)庫連接池,如DBCP、C3P0、Druid或Hikari等,可以在MyBatis的配置中進(jìn)行相應(yīng)的設(shè)置。這些連接池通常提供更好的性能和更豐富的功能,如連接監(jiān)控和統(tǒng)計等。
(3)UnpooledDataSource
如果不希望使用連接池,可以將數(shù)據(jù)源類型設(shè)置為UNPOOLED。
這種情況下,MyBatis會為每次查詢創(chuàng)建一個新的數(shù)據(jù)庫連接,并在查詢結(jié)束后關(guān)閉該連接。
不推薦使用。
(4)事務(wù)管理
MyBatis的事務(wù)管理也是連接池管理的一部分。當(dāng)使用事務(wù)時,MyBatis會根據(jù)事務(wù)的開始和結(jié)束來控制連接的獲取和釋放。如果事務(wù)成功提交,連接會被返回到連接池中;如果事務(wù)回滾,連接可能會被關(guān)閉。
(5)資源清理
MyBatis在執(zhí)行SQL語句后,會負(fù)責(zé)關(guān)閉PreparedStatement和ResultSet等資源,以釋放數(shù)據(jù)庫連接。這可以通過配置的資源清理策略來實現(xiàn),例如使用try-with-resources語句或顯式調(diào)用close方法。
7.如何理解MyBatis中的資源清理策略?
(1)更新數(shù)據(jù)時清除緩存
當(dāng)執(zhí)行數(shù)據(jù)更新操作(如INSERT、UPDATE或DELETE)時,應(yīng)確保相關(guān)的緩存被及時清除或更新,以避免臟讀或數(shù)據(jù)不一致的情況發(fā)生。
(2)合理配置緩存大小
為了避免緩存占用過多內(nèi)存,應(yīng)根據(jù)應(yīng)用的需求和服務(wù)器的性能來合理配置緩存的大小和清除策略。
(3)處理緩存并發(fā)問題
在并發(fā)環(huán)境下,需要特別注意緩存可能引起的問題,如臟讀或數(shù)據(jù)不一致??梢酝ㄟ^配置事務(wù)隔離級別或者使用樂觀鎖等機(jī)制來減少并發(fā)問題的發(fā)生。
(4)關(guān)閉自動提交
為了防止每次執(zhí)行SQL語句后都自動提交事務(wù),導(dǎo)致頻繁地打開和關(guān)閉數(shù)據(jù)庫連接,可以在MyBatis的配置中關(guān)閉自動提交功能。
(5)使用合適的資源清理策略
MyBatis提供了不同的資源清理策略,如基于時間、基于空間或基于計數(shù)等。選擇合適的策略可以幫助有效地管理資源,避免資源泄露。
8.如何在MyBatis中配置資源清理策略?
(1)數(shù)據(jù)庫連接資源管理
MyBatis本身不提供連接池,但可以與第三方連接池整合,比如常用的 c3p0、Druid 等。通過配置連接池的參數(shù),可以控制連接的獲取和釋放、最大連接數(shù)、空閑連接超時等。
(2)緩存資源管理
MyBatis 支持二級緩存,可以在標(biāo)簽中配置二級緩存的屬性,包括緩存刷新間隔、緩存過期時間等。
<!-- 開啟二級緩存 -->
<cache eviction="FIFO" flushInterval="60000" size="512"/>
(3)開啟自動提交
設(shè)置在執(zhí)行查詢語句后自動提交事務(wù),這樣可以及時釋放資源。
<setting name="autoCommit" value="true"/>
(4)使用SqlSessionFactoryBuilder建造者模式
使用 SqlSessionFactoryBuilder 來創(chuàng)建 SqlSessionFactory,在創(chuàng)建完成后對其進(jìn)行立即銷毀操作,這樣可以釋放資源并避免資源泄露。
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader);
// 使用 factory 創(chuàng)建 SqlSession
factory.close();
(5)手動釋放資源
在使用完畢后,顯式調(diào)用相關(guān)資源的關(guān)閉方法,如關(guān)閉 SqlSession、清理緩存等。
SqlSession session = sqlSessionFactory.openSession();
// 執(zhí)行數(shù)據(jù)庫操作
session.close();