MyBatis
本系列《最少必要面試題》
1. 什么是MyBatis
這個(gè)問題主要是對(duì)比JDBC來看
MyBatis是一個(gè)ORM(對(duì)象關(guān)系映射)框架,它內(nèi)部封裝了JDBC,開發(fā)時(shí)只需要關(guān)注SQL語句本身,不需要花費(fèi)精力去處理加載驅(qū)動(dòng),創(chuàng)建連接,創(chuàng)建statement等復(fù)雜的過程。開發(fā)人員不需要編寫原生態(tài)sql,可以嚴(yán)格控制sql執(zhí)行性能,靈活度高。
MyBatis可以使用xml或者注解來配置映射原生信息,將POJO映射成數(shù)據(jù)庫中的記錄,避免了幾乎所有的JDBC代碼和手動(dòng)設(shè)置的參數(shù)以及獲取結(jié)果集。
2. MyBatis的優(yōu)點(diǎn)
基于SQL語句編程,相對(duì)靈活(相對(duì)于hibernate),支持寫動(dòng)態(tài)sql語句并可重復(fù)使用。
減少代碼量,消除了冗余代碼。(類似于JDBC的封裝)
與Spring完美集成。
提供映射標(biāo)簽支持字段關(guān)系映射。
3. #{}和${}的區(qū)別是什么?
-
{}預(yù)編譯處理、是占位符,${}是字符串替換、是拼接符。
使用#{}可以有效的防止sql注入,提高系統(tǒng)的安全性。
Mybatis在處理#{}的時(shí)候會(huì)將sql中的#{}替換成?號(hào),調(diào)用PreparedStatement來賦值
/* SQL */
如:select * from user where name = #{userName};設(shè)userName=javapub
看日志我們可以看到解析時(shí)將#{userName}替換成了 ?
select * from user where name = ?;
然后再把 javapub 放進(jìn)去,外面加上單引號(hào)
Mybatis在處理{}替換成變量的值,調(diào)用Statement來賦值
/* SQL */
如:select * from user where name = #{userName};設(shè)userName=javapub
看日志可以發(fā)現(xiàn)就是直接把值拼接上去了
select * from user where name = javapub;
這極有可能發(fā)生sql注入,下面舉了一個(gè)簡單的sql注入案例
4. 一個(gè) Xml 映射文件,都會(huì)寫一個(gè) Dao 接口與之對(duì)應(yīng),這個(gè) Dao 接口的工作原理是什么?
Dao 接口就是人們常說的 Mapper 接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名就是映射文件中 MappedStatement 的 id 值,接口方法內(nèi)的參數(shù)就是傳遞給 sql 的參數(shù)。
接口里的方法是不能重載的,因?yàn)槭?strong>全限名+方法名的保存和尋找策略。
Dao接口的工作原理是JDK動(dòng)態(tài)代理,Mybatis運(yùn)行時(shí)會(huì)使用JDK動(dòng)態(tài)代理為Dao接口生成代理proxy對(duì)象,代理對(duì)象proxy會(huì)攔截接口方法,轉(zhuǎn)而執(zhí)行接口方法所對(duì)應(yīng)的MappedStatement所代表的sql,然后將sql執(zhí)行結(jié)果返回。
MappedStatement:MappedStatement維護(hù)了一條 <select|update|delete|insert>節(jié)點(diǎn)的封裝,包括了傳入?yún)?shù)映射配置、執(zhí)行的SQL語句、結(jié)果映射配置等信息。
<select id="selectAuthorLinkedHashMap" resultType="java.util.LinkedHashMap">
select id, username from author where id = #{value}
</select>
5. 如何獲取自動(dòng)生成的(主)鍵值?
用法:
在 <insert /> 標(biāo)簽中添加 useGeneratedKeys="true" 等屬性
<insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id"
parameterType="person" >
INSERT INTO person(name, pswd)
VALUE (#{name}, #{pswd})
</insert>
當(dāng) Mybatis 解析 xml節(jié)點(diǎn)是,讀到 insert 有配置時(shí),會(huì)判斷是否 有配置 useGeneratedKeys,如果有則會(huì)使用 Jdbc3KeyGenerator 作為sql回顯,否則會(huì)以 NoKeyGenerator 作為主鍵回顯。
底層封裝了JDBC獲取自增主鍵,即當(dāng)使用 prepareStatement 或者 Statement時(shí)候,可以通過 getGeneratedKeys 獲取 當(dāng)條插入語句的自增而成的主鍵。例子
Connection conn = DriverManager.getConnection(url, "root", "123456");
String[] columnNames = {"id", "name"};
PreparedStatement stmt = conn.prepareStatement(sql, columnNames);
stmt.setString(1, "jack wang");
stmt.executeUpdate();
ResultSet rs = stmt.getGeneratedKeys();
int id = 0;
if (rs.next()) {
id = rs.getInt(1);
System.out.println("----------" + id);
}
6. Mybatis 動(dòng)態(tài) sql 有什么用?有哪些動(dòng)態(tài) sql?執(zhí)行原理?
Mybatis 動(dòng)態(tài) sql 可以讓我們?cè)?Xml 映射文件內(nèi),以標(biāo)簽的形式編寫動(dòng)態(tài) sql,完成邏輯判斷和動(dòng)態(tài)拼接 sql 的功能。
Mybatis 提供了9種動(dòng)態(tài)sql標(biāo)簽: trim | where | set | foreach | if | choose | when | otherwise | bind。
其執(zhí)行原理為,使用 OGNL 從 sql 參數(shù)對(duì)象中計(jì)算表達(dá)式的值,根據(jù)表達(dá)式的值動(dòng)態(tài)拼接 sql,以此來完成動(dòng)態(tài) sql 的功能。
是不是有點(diǎn)懵,繼續(xù)閱讀:
科普:
OGNL 是 Object-Graph Navigation Language 的縮寫,對(duì)象-圖行導(dǎo)航語言。例如 #{} 語法。
OGNL 作用是在對(duì)象和視圖之間做數(shù)據(jù)的交互,可以存取對(duì)象的屬性和調(diào)用對(duì)象的方法,通過表達(dá)式可以迭代出整個(gè)對(duì)象的結(jié)構(gòu)圖。
參考一個(gè)很形象的例子。
有一個(gè)學(xué)生對(duì)象 student,屬性分別有 id = 10,name = '小明' 和 課程對(duì)象 course,其中 course 對(duì)象中屬性有:分?jǐn)?shù) score = 88,排名 rank = 5。
對(duì)象關(guān)系圖如下:
student
id:10
name:小明
course:
score:88
rank:5
當(dāng)上下文(環(huán)境)中的對(duì)象為 student 的時(shí)候,也就是在 Mybatis 中查詢時(shí)傳入的參數(shù)對(duì)象為 student 的時(shí)候:
通過 OGNL 表達(dá)式直接獲取上下文中對(duì)象的屬性值,比如:
{id} —> 10,相對(duì)于當(dāng)前上下文對(duì)象.getId(),即 student.getId() 。
{name} —> 小明。
{course.score} —> 88,相當(dāng)于 student.getCourse().getScore()。
所以,通過 OGNL 表達(dá)式,可以迭代出整個(gè)對(duì)象的結(jié)構(gòu)圖。
發(fā)布 《最少必要面試題》
7. 什么是Mybatis的一級(jí)、二級(jí)緩存?
一級(jí)緩存: 基于 PerpetualCache 的 HashMap 本地緩存,其存儲(chǔ)作用域?yàn)?Session,當(dāng) Session flush 或 close 之后,該 Session 中的所有 Cache 就將清空,默認(rèn)一級(jí)緩存是開啟的。
當(dāng)Mybaits與Spring整合的時(shí)候,不帶Spring事務(wù)的方法內(nèi),每次請(qǐng)求數(shù)據(jù)庫,都會(huì)新建一個(gè)SqlSession,這時(shí)候是使用不到一級(jí)緩存的。除了事務(wù)問題,還有調(diào)用了Sqlsession的修改、添加、刪除、commit()、close()等方法時(shí),一級(jí)緩存也會(huì)被清空。
二級(jí)緩存與一級(jí)緩存其機(jī)制相同,默認(rèn)也是采用 PerpetualCache,HashMap 存儲(chǔ),不同在于其存儲(chǔ)作用域?yàn)?Mapper(Namespace)。即使開啟了二級(jí)緩存,不同的sqlsession之間的緩存數(shù)據(jù)也不是想互訪就能互訪的,必須等到sqlsession關(guān)閉了以后,才會(huì)把其一級(jí)緩存中的數(shù)據(jù)寫入二級(jí)緩存。默認(rèn)不打開二級(jí)緩存。
現(xiàn)在大多數(shù)應(yīng)用都是支持分布式的,一般情況都是用中間件作為緩存層,比如redis。開啟 MyBatis 的二級(jí)緩存也會(huì)多一步序列化和反序列化,影響服務(wù)性能。
8. MyBatis的工作原理
一圖勝千文
[圖片上傳失敗...(image-2c3ddd-1655967208925)]
讀取 MyBatis 配置文件:mybatis-config.xml 為 MyBatis 的全局配置文件,配置了 MyBatis 的運(yùn)行環(huán)境等信息,例如數(shù)據(jù)庫連接信息。
加載映射文件。映射文件即 SQL 映射文件,該文件中配置了操作數(shù)據(jù)庫的 SQL 語句,需要在 MyBatis 配置文件 mybatis-config.xml 中加載。mybatis-config.xml 文件可以加載多個(gè)映射文件,每個(gè)文件對(duì)應(yīng)數(shù)據(jù)庫中的一張表。
構(gòu)造會(huì)話工廠:通過 MyBatis 的環(huán)境等配置信息構(gòu)建會(huì)話工廠 SqlSessionFactory。
創(chuàng)建會(huì)話對(duì)象:由會(huì)話工廠創(chuàng)建 SqlSession 對(duì)象,該對(duì)象中包含了執(zhí)行 SQL 語句的所有方法。
Executor 執(zhí)行器:MyBatis 底層定義了一個(gè) Executor 接口來操作數(shù)據(jù)庫,它將根據(jù) SqlSession 傳遞的參數(shù)動(dòng)態(tài)地生成需要執(zhí)行的 SQL 語句,同時(shí)負(fù)責(zé)查詢緩存的維護(hù)。
MappedStatement 對(duì)象:在 Executor 接口的執(zhí)行方法中有一個(gè) MappedStatement 類型的參數(shù),該參數(shù)是對(duì)映射信息的封裝,用于存儲(chǔ)要映射的 SQL 語句的 id、參數(shù)等信息。
輸入?yún)?shù)映射:輸入?yún)?shù)類型可以是 Map、List 等集合類型,也可以是基本數(shù)據(jù)類型和 POJO 類型。輸入?yún)?shù)映射過程類似于 JDBC 對(duì) preparedStatement 對(duì)象設(shè)置參數(shù)的過程。
輸出結(jié)果映射:輸出結(jié)果類型可以是 Map、 List 等集合類型,也可以是基本數(shù)據(jù)類型和 POJO 類型。輸出結(jié)果映射過程類似于 JDBC 對(duì)結(jié)果集的解析過程。
9. 什么是MyBatis的接口綁定?有哪些實(shí)現(xiàn)方式?
接口綁定,就是在 MyBatis 中任意定義接口,然后把接口里面的方法和SQL語句綁定,我們直接調(diào)用接口方法就可以,這樣比起原來了SqlSession提供的方法我們可以有更加靈活的選擇和設(shè)置。
接口綁定有兩種實(shí)現(xiàn)方式:
通過注解綁定,就是在接口的方法上面加上 @Select、@Update 等注解,里面包含Sql語句來綁定;
通過xml里面寫SQL來綁定, 在這種情況下,要指定xml映射文件里面的 namespace 必須為接口的全路徑名。當(dāng)Sql語句比較簡單時(shí)候,用注解綁定, 當(dāng)SQL語句比較復(fù)雜時(shí)候,用xml綁定,一般用xml綁定的比較多。
10. Mybatis的分頁原理
Mybatis 使用 RowBounds 對(duì)象進(jìn)行分頁,它是針對(duì)ResultSet結(jié)果集執(zhí)行的內(nèi)存分頁,而非物理分頁,所以一般不會(huì)使用??梢栽趕ql內(nèi)直接書寫帶有物理分頁的參數(shù)來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。
分頁插件的原理就是使用 MyBatis 提供的插件接口,實(shí)現(xiàn)自定義插件,在插件的攔截方法內(nèi),攔截待執(zhí)行的SQL,然后根據(jù)設(shè)置的 dialect(方言),和設(shè)置的分頁參數(shù),重寫SQL ,生成帶有分頁語句的SQL,執(zhí)行重寫后的SQL,從而實(shí)現(xiàn)分頁。
舉例:select * from student,攔截sql后重寫為:select t.* from (select * from student)t limit 0,10。
[圖片上傳失敗...(image-998e03-1655967208925)]
低谷蓄力