MyBatis面試題(2020)


MyBatis 簡介
MyBatis 是什么?
MyBatis 是一款優秀的持久層框架,一個半 ORM(對象關系映射)框架,它支持定制化
SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以
及獲取結果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生類型、接口和
Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。
ORM 是什么
ORM(Object Relational Mapping),對象關系映射,是一種為了解決關系型數據庫數據
與簡單 Java 對象(POJO)的映射關系的技術。簡單的說,ORM 是通過使用描述對象和數
據庫之間映射的元數據,將程序中的對象自動持久化到關系型數據庫中。
為什么說 Mybatis 是半自動 ORM 映射工具?它與全自動的區別在哪里?
Hibernate 屬於全自動 ORM 映射工具,使用 Hibernate 查詢關聯對象或者關聯集合對象
時,可以根據對象關系模型直接獲取,所以它是全自動的。
而 Mybatis 在查詢關聯對象或關聯集合對象時,需要手動編寫 sql 來完成,所以,稱之為
半自動 ORM 映射工具。
傳統 JDBC 開發存在的問題
頻繁創建數據庫連接對象、釋放,容易造成系統資源浪費,影響系統性能。可以使用連接池解決這個問題。但是使用 jdbc 需要自己實現連接池。
sql 語句定義、參數設置、結果集處理存在硬編碼。實際項目中 sql 語句變化的可能性較大,
一旦發生變化,需要修改 java 代碼,系統需要重新編譯,重新發布。不好維護。
使用 preparedStatement 向占有位符號傳參數存在硬編碼,因為 sql 語句的 where 條件不
一定,可能多也可能少,修改 sql 還要修改代碼,系統不易維護。
結果集處理存在重復代碼,處理麻煩。如果可以映射成 Java 對象會比較方便。
JDBC 編程有哪些不足之處,MyBatis 是如何解決這些問題的?
1、數據庫鏈接創建、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用數據庫連接
池可解決此問題。
解決:在 mybatis-config.xml 中配置數據鏈接池,使用連接池管理數據庫連接。
2、Sql 語句寫在代碼中造成代碼不易維護,實際應用 sql 變化的可能較大,sql 變動需要改
變 java 代碼。
解決:將 Sql 語句配置在 XXXXmapper.xml 文件中與 java 代碼分離。
3、向 sql 語句傳參數麻煩,因為 sql 語句的 where 條件不一定,可能多也可能少,占位符
需要和參數一一對應。
解決: Mybatis 自動將 java 對象映射至 sql 語句。4、對結果集解析麻煩,sql 變化導致解析代碼變化,且解析前需要遍歷,如果能將數據庫
記錄封裝成 pojo 對象解析比較方便。
解決:Mybatis 自動將 sql 執行結果映射至 java 對象。
Mybatis 優缺點
優點
與傳統的數據庫訪問技術相比,ORM 有以下優點:
基於 SQL 語句編程,相當靈活,不會對應用程序或者數據庫的現有設計造成任何影響,SQL
寫在 XML 里,解除 sql 與程序代碼的耦合,便於統一管理;提供 XML 標簽,支持編寫動
態 SQL 語句,並可重用
與 JDBC 相比,減少了 50%以上的代碼量,消除了 JDBC 大量冗余的代碼,不需要手動開
關連接
很好的與各種數據庫兼容(因為 MyBatis 使用 JDBC 來連接數據庫,所以只要 JDBC 支持
的數據庫 MyBatis 都支持)
提供映射標簽,支持對象與數據庫的 ORM 字段關系映射;提供對象關系映射標簽,支持對
象關系組件維護
能夠與 Spring 很好的集成
缺點SQL 語句的編寫工作量較大,尤其當字段多、關聯表多時,對開發人員編寫 SQL 語句的功
底有一定要求
SQL 語句依賴於數據庫,導致數據庫移植性差,不能隨意更換數據庫
MyBatis 框架適用場景
MyBatis 專注於 SQL 本身,是一個足夠靈活的 DAO 層解決方案。
對性能的要求很高,或者需求變化較多的項目,如互聯網項目,MyBatis 將是不錯的選擇。
Hibernate 和 MyBatis 的區別
相同點
都是對 jdbc 的封裝,都是持久層的框架,都用於 dao 層的開發。
不同點
映射關系
MyBatis 是一個半自動映射的框架,配置 Java 對象與 sql 語句執行結果的對應關系,多表
關聯關系配置簡單
Hibernate 是一個全表映射的框架,配置 Java 對象與數據庫表的對應關系,多表關聯關系
配置復雜
SQL 優化和移植性
Hibernate 對 SQL 語句封裝,提供了日志、緩存、級聯(級聯比 MyBatis 強大)等特性,此外還提供 HQL(Hibernate Query Language)操作數據庫,數據庫無關性支持好,但
會多消耗性能。如果項目需要支持多種數據庫,代碼開發量少,但 SQL 語句優化困難。
MyBatis 需要手動編寫 SQL,支持動態 SQL、處理列表、動態生成表名、支持存儲過程。
開發工作量相對大些。直接使用 SQL 語句操作數據庫,不支持數據庫無關性,但 sql 語句
優化容易。
開發難易程度和學習成本
Hibernate 是重量級框架,學習使用門檻高,適合於需求相對穩定,中小型的項目,比如:
辦公自動化系統
MyBatis 是輕量級框架,學習使用門檻低,適合於需求變化頻繁,大型的項目,比如:互
聯網電子商務系統
總結
MyBatis 是一個小巧、方便、高效、簡單、直接、半自動化的持久層框架,
Hibernate 是一個強大、方便、高效、復雜、間接、全自動化的持久層框架。
MyBatis 的解析和運行原理
MyBatis 編程步驟是什么樣的?
1、 創建 SqlSessionFactory2、 通過 SqlSessionFactory 創建 SqlSession
3、 通過 sqlsession 執行數據庫操作
4、 調用 session.commit()提交事務
5、 調用 session.close()關閉會話
請說說 MyBatis 的工作原理
在學習 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便於理解程序。MyBatis
的工作原理如下圖
1)讀取 MyBatis 配置文件:mybatis-config.xml 為 MyBatis 的全局配置文件,配置了
MyBatis 的運行環境等信息,例如數據庫連接信息。
2)加載映射文件。映射文件即 SQL 映射文件,該文件中配置了操作數據庫的 SQL 語句,
需要在 MyBatis 配置文件 mybatis-config.xml 中加載。mybatis-config.xml 文件可以
加載多個映射文件,每個文件對應數據庫中的一張表。3)構造會話工廠:通過 MyBatis 的環境等配置信息構建會話工廠 SqlSessionFactory。
4)創建會話對象:由會話工廠創建 SqlSession 對象,該對象中包含了執行 SQL 語句的
所有方法。
5)Executor 執行器:MyBatis 底層定義了一個 Executor 接口來操作數據庫,它將根據
SqlSession 傳遞的參數動態地生成需要執行的 SQL 語句,同時負責查詢緩存的維護。
6)MappedStatement 對象:在 Executor 接口的執行方法中有一個 MappedStatement
類型的參數,該參數是對映射信息的封裝,用於存儲要映射的 SQL 語句的 id、參數等信
息。
7)輸入參數映射:輸入參數類型可以是 Map、List 等集合類型,也可以是基本數據類型
和 POJO 類型。輸入參數映射過程類似於 JDBC 對 preparedStatement 對象設置參數
的過程。
8)輸出結果映射:輸出結果類型可以是 Map、 List 等集合類型,也可以是基本數據類型
和 POJO 類型。輸出結果映射過程類似於 JDBC 對結果集的解析過程。
MyBatis 的功能架構是怎樣的
我們把 Mybatis 的功能架構分為三層:API 接口層:提供給外部使用的接口 API,開發人員通過這些本地 API 來操縱數據庫。接口
層一接收到調用請求就會調用數據處理層來完成具體的數據處理。
數據處理層:負責具體的 SQL 查找、SQL 解析、SQL 執行和執行結果映射處理等。它主要
的目的是根據調用的請求完成一次數據庫操作。
基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置加載和緩存處理,這
些都是共用的東西,將他們抽取出來作為最基礎的組件。為上層的數據處理層提供最基礎的
支撐。
MyBatis 的框架架構設計是怎么樣的
這張圖從上往下看。MyBatis 的初始化,會從 mybatis-config.xml 配置文件,解析構造成
Configuration 這個類,就是圖中的紅框。
(1)加載配置:配置來源於兩個地方,一處是配置文件,一處是 Java 代碼的注解,將 SQL
的配置信息加載成為一個個 MappedStatement 對象(包括了傳入參數映射配置、執行的
SQL 語句、結果映射配置),存儲在內存中。
(2)SQL 解析:當 API 接口層接收到調用請求時,會接收到傳入 SQL 的 ID 和傳入對象(可
以是 Map、JavaBean 或者基本數據類型),Mybatis 會根據 SQL 的 ID 找到對應的
MappedStatement,然后根據傳入參數對象對 MappedStatement 進行解析,解析后可
以得到最終要執行的 SQL 語句和參數。(3)SQL 執行:將最終得到的 SQL 和參數拿到數據庫進行執行,得到操作數據庫的結果。
(4)結果映射:將操作數據庫的結果按照映射的配置進行轉換,可以轉換成 HashMap、
JavaBean 或者基本數據類型,並將最終結果返回。
為什么需要預編譯
定義:
SQL 預編譯指的是數據庫驅動在發送 SQL 語句和參數給 DBMS 之前對 SQL 語句進行
編譯,這樣 DBMS 執行 SQL 時,就不需要重新編譯。
為什么需要預編譯
JDBC 中使用對象 PreparedStatement 來抽象預編譯語句,使用預編譯。預編譯階段可
以優化 SQL 的執行。預編譯之后的 SQL 多數情況下可以直接執行,DBMS 不需要再次
編譯,越復雜的 SQL,編譯的復雜度將越大,預編譯階段可以合並多次操作為一個操作。
同時預編譯語句對象可以重復利用。把一個 SQL 預編譯后產生的 PreparedStatement
對象緩存下來,下次對於同一個 SQL,可以直接使用這個緩存的 PreparedState 對象。
Mybatis 默認情況下,將對所有的 SQL 進行預編譯。
Mybatis 都有哪些 Executor 執行器?它們之間的區別是什么?
Mybatis 有 三 種 基 本 的 Executor 執 行 器 , SimpleExecutor 、 ReuseExecutor 、
BatchExecutor。SimpleExecutor:每執行一次 update 或 select,就開啟一個 Statement 對象,用完立刻
關閉 Statement 對象。
ReuseExecutor:執行 update 或 select,以 sql 作為 key 查找 Statement 對象,存在就
使用,不存在就創建,用完后,不關閉 Statement 對象,而是放置於 Map<String,
Statement>內,供下一次使用。簡言之,就是重復使用 Statement 對象。
BatchExecutor:執行 update(沒有 select,JDBC 批處理不支持 select),將所有 sql 都
添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個 Statement
對象,每個 Statement 對象都是 addBatch()完畢后,等待逐一執行 executeBatch()批處理。
與 JDBC 批處理相同。
作用范圍:Executor 的這些特點,都嚴格限制在 SqlSession 生命周期范圍內。
Mybatis 中如何指定使用哪一種 Executor 執行器?
在 Mybatis 配置文件中,在設置(settings)可以指定默認的 ExecutorType 執行器類型,
也可以手動給 DefaultSqlSessionFactory 的創建 SqlSession 的方法傳遞 ExecutorType
類型參數,如 SqlSession openSession(ExecutorType execType)。
配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句
(prepared statements); BATCH 執行器將重用語句並執行批量更新。Mybatis 是否支持延遲加載?如果支持,它的實現原理是什么?
Mybatis 僅支持 association 關聯對象和 collection 關聯集合對象的延遲加載,association
指的就是一對一,collection 指的就是一對多查詢。在 Mybatis 配置文件中,可以配置是
否啟用延遲加載 lazyLoadingEnabled=true|false。
它的原理是,使用 CGLIB 創建目標對象的代理對象,當調用目標方法時,進入攔截器方法,
比如調用 a.getB().getName(),攔截器 invoke()方法發現 a.getB()是 null 值,那么就會單
獨發送事先保存好的查詢關聯 B 對象的 sql,把 B 查詢上來,然后調用 a.setB(b),於是 a
的對象 b 屬性就有值了,接着完成 a.getB().getName()方法的調用。這就是延遲加載的基
本原理。
當然了,不光是 Mybatis,幾乎所有的包括 Hibernate,支持延遲加載的原理都是一樣的。
映射器
#{}和${}的區別
#{}是占位符,預編譯處理;${}是拼接符,字符串替換,沒有預編譯處理。
Mybatis 在處理#{}時,#{}傳入參數是以字符串傳入,會將 SQL 中的#{}替換為?號,調用
PreparedStatement 的 set 方法來賦值。
Mybatis 在處理時,是原值傳入,就是把{}時,是原值傳入,就是把時,是原值傳入,就是把{}替換成變量的值,相當於 JDBC 中的 Statement 編譯
變量替換后,#{} 對應的變量自動加上單引號 ‘’;變量替換后,${} 對應的變量不會加
上單引號 ‘’
#{} 可以有效的防止 SQL 注入,提高系統安全性;${} 不能防止 SQL 注入
#{} 的變量替換是在 DBMS 中;${} 的變量替換是在 DBMS 外
模糊查詢 like 語句該怎么寫
(1)’%${question}%’ 可能引起 SQL 注入,不推薦
(2)"%"#{question}"%" 注意:因為#{…}解析成 sql 語句時候,會在變量外側自動加單
引號’ ',所以這里 % 需要使用雙引號" ",不能使用單引號 ’ ',不然會查不到任何結果。
(3)CONCAT(’%’,#{question},’%’) 使用 CONCAT()函數,推薦
(4)使用 bind 標簽
<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
<bind name="pattern" value="'%' + username + '%'" />
select id,sex,age,username,password from person where username LIKE#{pattern}
</select>
在 mapper 中如何傳遞多個參數
方法 1:順序傳參法
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{0} and dept_id = #{1}
</select>
#{}里面的數字代表傳入參數的順序。
這種方法不建議使用,sql 層表達不直觀,且一旦順序調整容易出錯。
方法 2:@Param 注解傳參法
public User selectUser(@Param("userName") String name, int @Param("deptId")
deptId);<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名稱對應的是注解@Param 括號里面修飾的名稱。
這種方法在參數不多的情況還是比較直觀的,推薦使用。
方法 3:Map 傳參法
public User selectUser(Map<String, Object> params);
<select
id="selectUser"
parameterType="java.util.Map"
resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名稱對應的是 Map 里面的 key 名稱。
這種方法適合傳遞多個參數,且參數易變能靈活傳遞的情況。方法 4:Java Bean 傳參法
public User selectUser(User user);
<select
id="selectUser"
parameterType="com.jourwon.pojo.User"
resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名稱對應的是 User 類里面的成員屬性。
這種方法直觀,需要建一個實體類,擴展不容易,需要加屬性,但代碼可讀性強,業務邏輯
處理方便,推薦使用。
Mybatis 如何執行批量操作
使用 foreach 標簽
foreach 的主要用在構建 in 條件中,它可以在 SQL 語句中進行迭代一個集合。foreach 標
簽的屬性主要有 item,index,collection,open,separator,close。item
表示集合中每一個元素進行迭代時的別名,隨便起的變量名;
index
指定一個名字,用於表示在迭代過程中,每次迭代到的位置,不常用;
open
表示該語句以什么開始,常用“(”;
separator 表示在每次進行迭代之間以什么符號作為分隔符,常用“,”;
close
表示以什么結束,常用“)”。
在使用 foreach 的時候最關鍵的也是最容易出錯的就是 collection 屬性,該屬性是必須指
定的,但是在不同情況下,該屬性的值是不一樣的,主要有一下 3 種情況:
如果傳入的是單參數且參數類型是一個 List 的時候,collection 屬性值為 list
如果傳入的是單參數且參數類型是一個 array 數組的時候,collection 的屬性值為 array
如果傳入的參數是多個的時候,我們就需要把它們封裝成一個 Map 了,當然單參數也可以
封裝成 map,實際上如果你在傳入參數的時候,在 MyBatis 里面也是會把它封裝成一個
Map 的,
map 的 key 就是參數名,所以這個時候 collection 屬性值就是傳入的 List 或 array 對象在
自己封裝的 map 里面的 key
具體用法如下:
<!-- 批量保存(foreach 插入多條數據兩種方法)
int addEmpsBatch(@Param("emps") List<Employee> emps); -->
<!-- MySQL 下批量保存,可以 foreach 遍歷 mysql 支持 values(),(),()語法 --> //推薦使
<insert id="addEmpsBatch">INSERT INTO emp(ename,gender,email,did)
VALUES
<foreach collection="emps" item="emp" separator=",">
(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert>
<!-- 這種方式需要數據庫連接屬性 allowMutiQueries=true 的支持
如 jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true -->
<insert id="addEmpsBatch">
<foreach collection="emps" item="emp" separator=";">
INSERT INTO emp(ename,gender,email,did)
VALUES(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert>
使用 ExecutorType.BATCH
Mybatis 內置的 ExecutorType 有 3 種,默認為 simple,該模式下它為每個語句的執行創建
一個新的預處理語句,單條提交 sql;而 batch 模式重復使用已經預處理的語句,並且批量
執行所有更新語句,顯然 batch 性能將更優;但 batch 模式也有自己的問題,比如在 Insert
操作時,在事務沒有提交之前,是沒有辦法獲取到自增的 id,這在某型情形下是不符合業務要求的
具體用法如下
//批量保存方法測試
@Test
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以執行批量操作的 sqlSession
SqlSession
openSession
=
sqlSessionFactory.openSession(ExecutorType.BATCH);
//批量保存執行前時間
long start = System.currentTimeMillis();
try {
EmployeeMapper
mapper
=
openSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 1000; i++) {
mapper.addEmp(new
Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
}openSession.commit();
long end = System.currentTimeMillis();
//批量保存執行后的時間
System.out.println("執行時長" + (end - start));
//批量 預編譯 sql 一次==》設置參數==》10000 次==》執行 1 次
677
//非批量 (預編譯=設置參數=執行 )==》10000 次
1121
} finally {
openSession.close();
}
}
mapper 和 mapper.xml 如下
public interface EmployeeMapper {
//批量保存員工
Long addEmp(Employee employee);
}
<mapper namespace="com.jourwon.mapper.EmployeeMapper"
<!--批量保存員工 -->
<insert id="addEmp">insert into employee(lastName,email,gender)
values(#{lastName},#{email},#{gender})
</insert>
</mapper>
如何獲取生成的主鍵
對於支持主鍵自增的數據庫(MySQL)
<insert id="insertUser" useGeneratedKeys="true" keyProperty="userId" >
insert into user(
user_name, user_password, create_time)
values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>
parameterType 可以不寫,Mybatis 可以推斷出傳入的數據類型。如果想要訪問主鍵,那
么應當 parameterType 應當是 java 實體或者 Map。這樣數據在插入之后 可以通過 ava
實體或者 Map 來獲取主鍵值。通過 getUserId 獲取主鍵
不支持主鍵自增的數據庫(Oracle)
對於像 Oracle 這樣的數據,沒有提供主鍵自增的功能,而是使用序列的方式獲取自增主鍵。可以使用<selectKey>標簽來獲取主鍵的值,這種方式不僅適用於不提供主鍵自增功能的
數據庫,也適用於提供主鍵自增功能的數據庫
<selectKey>一般的用法
<selectKey
keyColumn="id"
resultType="long"
keyProperty="id"
order="BEFORE">
</selectKey>
1
2
屬性
描述
keyProperty selectKey 語句結果應該被設置的目標屬性。如果希望得到多個生成的列,也
可以是逗號分隔的屬性名稱列表。
keyColumn 匹配屬性的返回結果集中的列名稱。如果希望得到多個生成的列,也可以是逗
號分隔的屬性名稱列表。
resultType 結果的類型,MyBatis 通常可以推算出來。MyBatis 允許任何簡單類型用作
主鍵的類型,包括字符串。如果希望作用於多個生成的列,則可以使用一個包含期望屬性的
Object 或一個 Map。
order
值可為 BEFORE 或 AFTER。如果是 BEFORE,那么它會先執行 selectKey 設置
keyProperty 然后執行插入語句。如果為 AFTER 則相反。
statementType 使用何種語句類型,默認 PREPARED。 有 STATEMENT,PREPARED 和
CALLABLE 語句的映射類型。
<insert id="insertUser" ><selectKey
keyColumn="id"
resultType="long"
keyProperty="userId"
order="BEFORE">
SELECT USER_ID.nextval as id from dual
</selectKey>
insert into user(
user_id,user_name, user_password, create_time)
values(#{userId},#{userName}, #{userPassword} , #{createTime, jdbcType=
TIMESTAMP})
</insert>
此時會將 Oracle 生成的主鍵值賦予 userId 變量。這個 userId 就是 USER 對象的屬性,這
樣就可以將生成的主鍵值返回了。如果僅僅是在 insert 語句中使用但是不返回,此時
keyProperty=“任意自定義變量名”,resultType 可以不寫。
Oracle 數據庫中的值要設置為 BEFORE ,這是因為 Oracle 中需要先從序列獲取值,然
后將值作為主鍵插入到數據庫中。
擴展
如果 Mysql 使用 selectKey 的方式獲取主鍵,需要注意下面兩點:
order : AFTER
獲取遞增主鍵值 :SELECT LAST_INSERT_ID()當實體類中的屬性名和表中的字段名不一樣 ,怎么辦
第 1 種: 通過在查詢的 SQL 語句中定義字段名的別名,讓字段名的別名和實體類的屬性名
一致。
<select
id="getOrder"
parameterType="int"
resultType="com.jourwon.pojo.Order">
select order_id id, order_no orderno ,order_price price form orders where
order_id=#{id};
</select>
第 2 種: 通過<resultMap>來映射字段名和實體類屬性名的一一對應的關系。
<select id="getOrder" parameterType="int" resultMap="orderResultMap">
select * from orders where order_id=#{id}
</select>
<resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
<!–用 id 屬性來映射主鍵字段–>
<id property="id" column="order_id">
<!–用 result 屬性來映射非主鍵字段,property 為實體類屬性名,column 為數據庫表
中的屬性–><result property ="orderno" column ="order_no"/>
<result property="price" column="order_price" />
</reslutMap>
Mapper 編寫有哪幾種方式?
第一種:接口實現類繼承 SqlSessionDaoSupport:使用此種方法需要編寫 mapper 接口,
mapper 接口實現類、mapper.xml 文件。
(1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置
<mappers>
<mapper resource="mapper.xml 文件的地址" />
<mapper resource="mapper.xml 文件的地址" />
</mappers>
(2)定義 mapper 接口
(3)實現類集成 SqlSessionDaoSupport
mapper 方法中可以 this.getSqlSession()進行數據增刪改查。
(4)spring 配置<bean id=" " class="mapper 接口的實現">
<property name="sqlSessionFactory"
ref="sqlSessionFactory"></property>
</bean>
第二種:使用 org.mybatis.spring.mapper.MapperFactoryBean:
(1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置,如果 mapper.xml 和 mappre
接口的名稱相同且在同一個目錄,這里可以不用配置
<mappers>
<mapper resource="mapper.xml 文件的地址" />
<mapper resource="mapper.xml 文件的地址" />
</mappers>
(2)定義 mapper 接口:
(3)mapper.xml 中的 namespace 為 mapper 接口的地址
(4)mapper 接口中的方法名和 mapper.xml 中的定義的 statement 的 id 保持一致(5)Spring 中定義
<bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mapper 接口地址" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
第三種:使用 mapper 掃描器:
(1)mapper.xml 文件編寫:
mapper.xml 中的 namespace 為 mapper 接口的地址;
mapper 接口中的方法名和 mapper.xml 中的定義的 statement 的 id 保持一致;
如果將 mapper.xml 和 mapper 接口的名稱保持一致則不用在 sqlMapConfig.xml 中
進行配置。
(2)定義 mapper 接口:
注意 mapper.xml 的文件名和 mapper 的接口名稱保持一致,且放在同一個目錄(3)配置 mapper 掃描器:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper 接口包地址
"></property>
<property name="sqlSessionFactoryBeanName"
value="sqlSessionFactory"/>
</bean>
(4)使用掃描器后從 spring 容器中獲取 mapper 的實現對象。
什么是 MyBatis 的接口綁定?有哪些實現方式?
接口綁定,就是在 MyBatis 中任意定義接口,然后把接口里面的方法和 SQL 語句綁定,我
們直接調用接口方法就可以,這樣比起原來了 SqlSession 提供的方法我們可以有更加靈活
的選擇和設置。
接口綁定有兩種實現方式
通過注解綁定,就是在接口的方法上面加上 @Select、@Update 等注解,里面包含 Sql
語句來綁定;
通過 xml 里面寫 SQL 來綁定, 在這種情況下,要指定 xml 映射文件里面的 namespace必須為接口的全路徑名。當 Sql 語句比較簡單時候,用注解綁定, 當 SQL 語句比較復雜時
候,用 xml 綁定,一般用 xml 綁定的比較多。
使用 MyBatis 的 mapper 接口調用時有哪些要求?
1、Mapper 接口方法名和 mapper.xml 中定義的每個 sql 的 id 相同。
2、Mapper 接口方法的輸入參數類型和 mapper.xml 中定義的每個 sql 的 parameterType
的類型相同。
3、Mapper 接口方法的輸出參數類型和 mapper.xml 中定義的每個 sql 的 resultType 的類
型相同。
4、Mapper.xml 文件中的 namespace 即是 mapper 接口的類路徑。
最佳實踐中,通常一個 Xml 映射文件,都會寫一個 Dao 接口與之對應,請問,這個 Dao
接口的工作原理是什么?Dao 接口里的方法,參數不同時,方法能重載嗎
Dao 接口,就是人們常說的 Mapper 接口,接口的全限名,就是映射文件中的 namespace
的值,接口的方法名,就是映射文件中 MappedStatement 的 id 值,接口方法內的參數,
就是傳遞給 sql 的參數。Mapper 接口是沒有實現類的,當調用接口方法時,接口全限名+
方 法 名 拼 接 字 符 串 作 為 key 值 , 可 唯 一 定 位 一 個 MappedStatement , 舉 例 :
com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace 為
com.mybatis3.mappers.StudentDao 下面 id = findStudentById 的 MappedStatement。在 Mybatis 中,每一個<select>、<insert>、<update>、<delete>標簽,都會被解析為
一個 MappedStatement 對象。
Dao 接口里的方法,是不能重載的,因為是全限名+方法名的保存和尋找策略。
Dao 接口的工作原理是 JDK 動態代理,Mybatis 運行時會使用 JDK 動態代理為 Dao 接口
生成代理 proxy 對象,代理對象 proxy 會攔截接口方法,轉而執行 MappedStatement 所
代表的 sql,然后將 sql 執行結果返回。
Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重復?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重復;如果沒有配置
namespace,那么 id 不能重復;畢竟 namespace 不是必須的,只是最佳實踐而已。
原因就是 namespace+id 是作為 Map<String, MappedStatement>的 key 使用的,如果
沒有 namespace,就剩下 id,那么,id 重復會導致數據互相覆蓋。有了 namespace,自
然 id 就可以重復,namespace 不同,namespace+id 自然也就不同。
簡述 Mybatis 的 Xml 映射文件和 Mybatis 內部數據結構之間的映射關系?
答:Mybatis 將所有 Xml 配置信息都封裝到 All-In-One 重量級對象 Configuration 內部。
在 Xml 映射文件中,<parameterMap>標簽會被解析為 ParameterMap 對象,其每個子
元素會被解析為 ParameterMapping 對象。<resultMap>標簽會被解析為 ResultMap 對
象,其每個子元素會被解析為 ResultMapping 對象。每一個<select>、<insert>、<update>、<delete>標簽均會被解析為 MappedStatement 對象,標簽內的 sql 會被解
析為 BoundSql 對象。
Mybatis 是如何將 sql 執行結果封裝為目標對象並返回的?都有哪些映射形式?
第一種是使用<resultMap>標簽,逐一定義列名和對象屬性名之間的映射關系。
第二種是使用 sql 列的別名功能,將列別名書寫為對象屬性名,比如 T_NAME AS NAME,
對象屬性名一般是 name,小寫,但是列名不區分大小寫,Mybatis 會忽略列名大小寫,智
能找到與之對應對象屬性名,你甚至可以寫成 T_NAME AS NaMe,Mybatis 一樣可以正
常工作。
有了列名與屬性名的映射關系后,Mybatis 通過反射創建對象,同時使用反射給對象的屬性
逐一賦值並返回,那些找不到映射關系的屬性,是無法完成賦值的。
Xml 映射文件中,除了常見的 select|insert|updae|delete 標簽之外,還有哪些標簽?
還 有 很 多 其 他 的 標 簽 , <resultMap> 、 <parameterMap> 、 <sql> 、 <include> 、
<selectKey>
sql
9
trim|where|set|foreach|if|choose|when|otherwise|bind 等,其中<sql>為 sql 片段標簽,
通過<include>標簽引入 sql 片段,<selectKey>為不支持自增的主鍵生成策略標簽。
Mybatis 映射文件中,如果 A 標簽通過 include 引用了 B 標簽的內容,請問,B 標簽能否
定義在 A 標簽的后面,還是說必須定義在 A 標簽的前面?雖然 Mybatis 解析 Xml 映射文件是按照順序解析的,但是,被引用的 B 標簽依然可以定義
在任何地方,Mybatis 都可以正確識別。
原理是,Mybatis 解析 A 標簽,發現 A 標簽引用了 B 標簽,但是 B 標簽尚未解析到,尚不
存在,此時,Mybatis 會將 A 標簽標記為未解析狀態,然后繼續解析余下的標簽,包含 B
標簽,待所有標簽解析完畢,Mybatis 會重新解析那些被標記為未解析的標簽,此時再解析
A 標簽時,B 標簽已經存在,A 標簽也就可以正常解析完成了。
高級查詢
MyBatis 實現一對一,一對多有幾種方式,怎么操作的?
有聯合查詢和嵌套查詢。聯合查詢是幾個表聯合查詢,只查詢一次,通過在 resultMap 里
面的 association,collection 節點配置一對一,一對多的類就可以完成
嵌套查詢是先查一個表,根據這個表里面的結果的外鍵 id,去再另外一個表里面查詢數據,
也是通過配置 association,collection,但另外一個表的查詢通過 select 節點配置。
Mybatis 是否可以映射 Enum 枚舉類?
Mybatis 可以映射枚舉類,不單可以映射枚舉類,Mybatis 可以映射任何對象到表的一列
上。映射方式為自定義一個 TypeHandler,實現 TypeHandler 的 setParameter()和
getResult()接口方法。
TypeHandler 有兩個作用,一是完成從 javaType 至 jdbcType 的轉換,二是完成 jdbcType至 javaType 的轉換,體現為 setParameter()和 getResult()兩個方法,分別代表設置 sql
問號占位符參數和獲取列查詢結果。
動態 SQL
Mybatis 動態 sql 是做什么的?都有哪些動態 sql?能簡述一下動態 sql 的執行原理不?
Mybatis 動態 sql 可以讓我們在 Xml 映射文件內,以標簽的形式編寫動態 sql,完成邏輯判
斷 和 動 態 拼 接 sql 的 功 能 , Mybatis 提 供 了 9 種 動 態 sql 標 簽
trim|where|set|foreach|if|choose|when|otherwise|bind。
其執行原理為,使用 OGNL 從 sql 參數對象中計算表達式的值,根據表達式的值動態拼接
sql,以此來完成動態 sql 的功能。
插件模塊
Mybatis 是如何進行分頁的?分頁插件的原理是什么?
Mybatis 使用 RowBounds 對象進行分頁,它是針對 ResultSet 結果集執行的內存分頁,
而非物理分頁,可以在 sql 內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使
用分頁插件來完成物理分頁。
分頁插件的基本原理是使用 Mybatis 提供的插件接口,實現自定義插件,在插件的攔截方
法內攔截待執行的 sql,然后重寫 sql,根據 dialect 方言,添加對應的物理分頁語句和物理
分頁參數。舉例:select * from student,攔截 sql 后重寫為:select t.* from (select * from student)
t limit 0, 10
簡述 Mybatis 的插件運行原理,以及如何編寫一個插件。
Mybatis 僅可以編寫針對 ParameterHandler、ResultSetHandler、StatementHandler、
Executor 這 4 種接口的插件,Mybatis 使用 JDK 的動態代理,為需要攔截的接口生成代理
對象以實現接口方法攔截功能,每當執行這 4 種接口對象的方法時,就會進入攔截方法,
具體就是 InvocationHandler 的 invoke()方法,當然,只會攔截那些你指定需要攔截的方
法。
實現 Mybatis 的 Interceptor 接口並復寫 intercept()方法,然后在給插件編寫注解,指定
要攔截哪一個接口的哪些方法即可,記住,別忘了在配置文件中配置你編寫的插件。
緩存
Mybatis 的一級、二級緩存
1)一級緩存: 基於 PerpetualCache 的 HashMap 本地緩存,其存儲作用域為 Session,
當 Session flush 或 close 之后,該 Session 中的所有 Cache 就將清空,默認打開一
級緩存。
2)二級緩存與一級緩存其機制相同,默認也是采用 PerpetualCache,HashMap 存儲,
不同在於其存儲作用域為 Mapper(Namespace),並且可自定義存儲源,如 Ehcache。默
認不打開二級緩存,要開啟二級緩存,使用二級緩存屬性類需要實現 Serializable 序列化接口(可用來保存對象的狀態),可在它的映射文件中配置<cache/> ;
3)對於緩存數據更新機制,當某一個作用域(一級緩存 Session/二級緩存 Namespaces)
的進行了 C/U/D 操作后,默認該作用域下所有 select 中的緩存將被 clear。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM