面渣逆襲:MyBatis連環(huán)20問,這誰頂?shù)米。?/h1>
大家好,今天我們的主角是MyBatis,作為當(dāng)前國內(nèi)最流行的ORM框架,是我們這些crud選手最趁手的工具,趕緊來看看面試都會問哪些問題吧。
基礎(chǔ)
1.說說什么是MyBatis?

MyBatis logo 先吹一下:
Mybatis 是一個半 ORM(對象關(guān)系映射)框架,它內(nèi)部封裝了 JDBC,開發(fā)時只需要關(guān)注 SQL 語句本身,不需要花費精力去處理加載驅(qū)動、創(chuàng)建連接、創(chuàng)建statement 等繁雜的過程。程序員直接編寫原生態(tài) sql,可以嚴(yán)格控制 sql 執(zhí)行性能,靈活度高。
MyBatis 可以使用 XML 或注解來配置和映射原生信息,將 POJO 映射成數(shù)據(jù)庫中的記錄,避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。
再說一下缺點
SQL語句的編寫工作量較大,尤其當(dāng)字段多、關(guān)聯(lián)表多時,對開發(fā)人員編寫SQL語句的功底有一定要求 SQL語句依賴于數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫移植性差,不能隨意更換數(shù)據(jù)庫
ORM是什么?

ORM簡單示意圖 ORM(Object Relational Mapping),對象關(guān)系映射,是一種為了解決關(guān)系型數(shù)據(jù)庫數(shù)據(jù)與簡單Java對象(POJO)的映射關(guān)系的技術(shù)。簡單來說,ORM是通過使用描述對象和數(shù)據(jù)庫之間映射的元數(shù)據(jù),將程序中的對象自動持久化到關(guān)系型數(shù)據(jù)庫中。
為什么說Mybatis是半自動ORM映射工具?它與全自動的區(qū)別在哪里?
Hibernate屬于全自動ORM映射工具,使用Hibernate查詢關(guān)聯(lián)對象或者關(guān)聯(lián)集合對象時,可以根據(jù)對象關(guān)系模型直接獲取,所以它是全自動的。 而Mybatis在查詢關(guān)聯(lián)對象或關(guān)聯(lián)集合對象時,需要手動編寫SQL來完成,所以,被稱之為半自動ORM映射工具。
JDBC編程有哪些不足之處,MyBatis是如何解決的?

JDBC編程的不足 1、數(shù)據(jù)連接創(chuàng)建、釋放頻繁造成系統(tǒng)資源浪費從而影響系統(tǒng)性能 解決:在mybatis-config.xml中配置數(shù)據(jù)鏈接池,使用連接池統(tǒng)一管理數(shù)據(jù)庫連接。
2、sql語句寫在代碼中造成代碼不易維護(hù) 解決:將sql語句配置在XXXXmapper.xml文件中與java代碼分離。
3、向sql語句傳參數(shù)麻煩,因為sql語句的where條件不一定,可能多也可能少,占位符需要和參數(shù)一一對應(yīng)。 解決:Mybatis自動將java對象映射至sql語句。
4、對結(jié)果集解析麻煩,sql變化導(dǎo)致解析代碼變化,且解析前需要遍歷,如果能將數(shù)據(jù)庫記錄封裝成pojo對象解析比較方便。 解決:Mybatis自動將sql執(zhí)行結(jié)果映射至java對象。
2.Hibernate 和 MyBatis 有什么區(qū)別?
PS:直接用Hibernate的應(yīng)該不多了吧,畢竟大家都是“敏捷開發(fā)”,但架不住面試愛問。
相同點
都是對jdbc的封裝,都是應(yīng)用于持久層的框架。

這還用說? 不同點
映射關(guān)系
MyBatis 是一個半自動映射的框架,配置Java對象與sql語句執(zhí)行結(jié)果的對應(yīng)關(guān)系,多表關(guān)聯(lián)關(guān)系配置簡單 Hibernate 是一個全表映射的框架,配置Java對象與數(shù)據(jù)庫表的對應(yīng)關(guān)系,多表關(guān)聯(lián)關(guān)系配置復(fù)雜
SQL優(yōu)化和移植性
Hibernate 對SQL語句封裝,提供了日志、緩存、級聯(lián)(級聯(lián)比 MyBatis 強(qiáng)大)等特性,此外還提供 HQL(Hibernate Query Language)操作數(shù)據(jù)庫,數(shù)據(jù)庫無關(guān)性支持好,但會多消耗性能。如果項目需要支持多種數(shù)據(jù)庫,代碼開發(fā)量少,但SQL語句優(yōu)化困難。 MyBatis 需要手動編寫 SQL,支持動態(tài) SQL、處理列表、動態(tài)生成表名、支持存儲過程。開發(fā)工作量相對大些。直接使用SQL語句操作數(shù)據(jù)庫,不支持?jǐn)?shù)據(jù)庫無關(guān)性,但sql語句優(yōu)化容易。
MyBatis和Hibernate的適用場景?

Mybatis vs Hibernate Hibernate 是標(biāo)準(zhǔn)的ORM框架,SQL編寫量較少,但不夠靈活,適合于需求相對穩(wěn)定,中小型的軟件項目,比如:辦公自動化系統(tǒng)
MyBatis 是半ORM框架,需要編寫較多SQL,但是比較靈活,適合于需求變化頻繁,快速迭代的項目,比如:電商網(wǎng)站
3.MyBatis使用過程?生命周期?
MyBatis基本使用的過程大概可以分為這么幾步:

Mybatis基本使用步驟 1、 創(chuàng)建SqlSessionFactory
可以從配置或者直接編碼來創(chuàng)建SqlSessionFactory
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2、 通過SqlSessionFactory創(chuàng)建SqlSession
SqlSession(會話)可以理解為程序和數(shù)據(jù)庫之間的橋梁
SqlSession session = sqlSessionFactory.openSession();
3、 通過sqlsession執(zhí)行數(shù)據(jù)庫操作
可以通過 SqlSession 實例來直接執(zhí)行已映射的 SQL 語句:
Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
更常用的方式是先獲取Mapper(映射),然后再執(zhí)行SQL語句:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
4、 調(diào)用session.commit()提交事務(wù)
如果是更新、刪除語句,我們還需要提交一下事務(wù)。
5、 調(diào)用session.close()關(guān)閉會話
最后一定要記得關(guān)閉會話。
MyBatis生命周期?
上面提到了幾個MyBatis的組件,一般說的MyBatis生命周期就是這些組件的生命周期。
SqlSessionFactoryBuilder
一旦創(chuàng)建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 實例的生命周期只存在于方法的內(nèi)部。
SqlSessionFactory
SqlSessionFactory 是用來創(chuàng)建SqlSession的,相當(dāng)于一個數(shù)據(jù)庫連接池,每次創(chuàng)建SqlSessionFactory都會使用數(shù)據(jù)庫資源,多次創(chuàng)建和銷毀是對資源的浪費。所以SqlSessionFactory是應(yīng)用級的生命周期,而且應(yīng)該是單例的。
SqlSession
SqlSession相當(dāng)于JDBC中的Connection,SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的生命周期是一次請求或一個方法。
Mapper
映射器是一些綁定映射語句的接口。映射器接口的實例是從 SqlSession 中獲得的,它的生命周期在sqlsession事務(wù)方法之內(nèi),一般會控制在方法級。

MyBatis主要組件生命周期 當(dāng)然,萬物皆可集成Spring,MyBatis通常也是和Spring集成使用,Spring可以幫助我們創(chuàng)建線程安全的、基于事務(wù)的 SqlSession 和映射器,并將它們直接注入到我們的 bean 中,我們不需要關(guān)心它們的創(chuàng)建過程和生命周期,那就是另外的故事了。
ps:接下來看看Mybatis的基本使用,不會有人不會吧?不會吧!

這個應(yīng)該會 4.在mapper中如何傳遞多個參數(shù)?

mapper傳遞多個參數(shù)方法 方法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>
#{}里面的數(shù)字代表傳入?yún)?shù)的順序。 這種方法不建議使用,sql層表達(dá)不直觀,且一旦順序調(diào)整容易出錯。
方法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>
#{}里面的名稱對應(yīng)的是注解@Param括號里面修飾的名稱。 這種方法在參數(shù)不多的情況還是比較直觀的,(推薦使用)。
方法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>
#{}里面的名稱對應(yīng)的是Map里面的key名稱。 這種方法適合傳遞多個參數(shù),且參數(shù)易變能靈活傳遞的情況。
方法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>
#{}里面的名稱對應(yīng)的是User類里面的成員屬性。 這種方法直觀,需要建一個實體類,擴(kuò)展不容易,需要加屬性,但代碼可讀性強(qiáng),業(yè)務(wù)邏輯處理方便,推薦使用。(推薦使用)。
5.實體類屬性名和表中字段名不一樣 ,怎么辦?
第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 中的<result>來映射字段名和實體類屬性名的一一對應(yīng)的關(guān)系。
<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為數(shù)據(jù)庫表中的屬性–>
<result property ="orderno" column ="order_no"/>
<result property="price" column="order_price" />
</reslutMap>
6.Mybatis是否可以映射Enum枚舉類?
Mybatis當(dāng)然可以映射枚舉類,不單可以映射枚舉類,Mybatis可以映射任何對象到表的一列上。映射方式為自定義一個TypeHandler,實現(xiàn)TypeHandler的setParameter()和getResult()接口方法。 TypeHandler有兩個作用,一是完成從javaType至jdbcType的轉(zhuǎn)換,二是完成jdbcType至javaType的轉(zhuǎn)換,體現(xiàn)為setParameter()和getResult()兩個方法,分別代表設(shè)置sql問號占位符參數(shù)和獲取列查詢結(jié)果。
7.#{}和${}的區(qū)別?

#{}和${}比較 #{}是占位符,預(yù)編譯處理;${}是拼接符,字符串替換,沒有預(yù)編譯處理。 Mybatis在處理#{}時,#{}傳入?yún)?shù)是以字符串傳入,會將SQL中的#{}替換為?號,調(diào)用PreparedStatement的set方法來賦值。 #{} 可以有效的防止SQL注入,提高系統(tǒng)安全性;${} 不能防止SQL 注入 #{} 的變量替換是在DBMS 中;${} 的變量替換是在 DBMS 外
8.模糊查詢like語句該怎么寫?
concat拼接like 1 ’%${question}%’ 可能引起SQL注入,不推薦 2 "%"#{question}"%" 注意:因為#{…}解析成sql語句時候,會在變量外側(cè)自動加單引號’ ',所以這里 % 需要使用雙引號" ",不能使用單引號 ’ ',不然會查不到任何結(jié)果。 3 CONCAT(’%’,#{question},’%’) 使用CONCAT()函數(shù),(推薦?) 4 使用bind標(biāo)簽(不推薦)
<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>
9.Mybatis能執(zhí)行一對一、一對多的關(guān)聯(lián)查詢嗎?
當(dāng)然可以,不止支持一對一、一對多的關(guān)聯(lián)查詢,還支持多對多、多對一的關(guān)聯(lián)查詢。

MyBatis級聯(lián) 一對一<association>
比如訂單和支付是一對一的關(guān)系,這種關(guān)聯(lián)的實現(xiàn):
實體類:
public class Order {
private Integer orderId;
private String orderDesc;
/**
* 支付對象
*/
private Pay pay;
//……
}
結(jié)果映射
<!-- 訂單resultMap -->
<resultMap id="peopleResultMap" type="cn.fighter3.entity.Order">
<id property="orderId" column="order_id" />
<result property="orderDesc" column="order_desc"/>
<!--一對一結(jié)果映射-->
<association property="pay" javaType="cn.fighter3.entity.Pay">
<id column="payId" property="pay_id"/>
<result column="account" property="account"/>
</association>
</resultMap>
查詢就是普通的關(guān)聯(lián)查
<select id="getTeacher" resultMap="getTeacherMap" parameterType="int">
select * from order o
left join pay p on o.order_id=p.order_id
where o.order_id=#{orderId}
</select>
一對多<collection>
比如商品分類和商品,是一對多的關(guān)系。
查詢
查詢就是一個普通的關(guān)聯(lián)查詢
<!-- 關(guān)聯(lián)查詢分類和產(chǎn)品表 -->
<select id="listCategory" resultMap="categoryBean">
select c.*, p.* from category_ c left join product_ p on c.id = p.cid
</select>
實體類
public class Category {
private int categoryId;
private String categoryName;
/**
* 商品列表
**/
List<Product> products;
//……
}
結(jié)果映射
<resultMap type="Category" id="categoryBean">
<id column="categoryId" property="category_id" />
<result column="categoryName" property="category_name" />
<!-- 一對多的關(guān)系 -->
<!-- property: 指的是集合屬性的值, ofType:指的是集合中元素的類型 -->
<collection property="products" ofType="Product">
<id column="product_id" property="productId" />
<result column="productName" property="productName" />
<result column="price" property="price" />
</collection>
</resultMap>
那么多對一、多對多怎么實現(xiàn)呢?還是利用<association>和<collection>,篇幅所限,這里就不展開了。
10.Mybatis是否支持延遲加載?原理?
Mybatis支持association關(guān)聯(lián)對象和collection關(guān)聯(lián)集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啟用延遲加載lazyLoadingEnabled=true|false。 它的原理是,使用CGLIB創(chuàng)建目標(biāo)對象的代理對象,當(dāng)調(diào)用目標(biāo)方法時,進(jìn)入攔截器方法,比如調(diào)用a.getB().getName(),攔截器invoke()方法發(fā)現(xiàn)a.getB()是null值,那么就會單獨發(fā)送事先保存好的查詢關(guān)聯(lián)B對象的sql,把B查詢上來,然后調(diào)用a.setB(b),于是a的對象b屬性就有值了,接著完成a.getB().getName()方法的調(diào)用。這就是延遲加載的基本原理。 當(dāng)然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。
11.如何獲取生成的主鍵?
新增標(biāo)簽中添加:keyProperty=" ID " 即可
<insert id="insert" useGeneratedKeys="true" keyProperty="userId" >
insert into user(
user_name, user_password, create_time)
values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>
這時候就可以完成回填主鍵
mapper.insert(user);
user.getId;
12.MyBatis支持動態(tài)SQL嗎?
MyBatis中有一些支持動態(tài)SQL的標(biāo)簽,它們的原理是使用OGNL從SQL參數(shù)對象中計算表達(dá)式的值,根據(jù)表達(dá)式的值動態(tài)拼接SQL,以此來完成動態(tài)SQL的功能。

MyBatis批量操作 第一種方法:使用foreach標(biāo)簽
foreach的主要用在構(gòu)建in條件中,它可以在SQL語句中進(jìn)行迭代一個集合。foreach標(biāo)簽的屬性主要有item,index,collection,open,separator,close。
item?? 表示集合中每一個元素進(jìn)行迭代時的別名,隨便起的變量名; index?? 指定一個名字,用于表示在迭代過程中,每次迭代到的位置,不常用; open?? 表示該語句以什么開始,常用“(”; separator 表示在每次進(jìn)行迭代之間以什么符號作為分隔符,常用“,”; close?? 表示以什么結(jié)束,常用“)”。
在使用foreach的時候最關(guān)鍵的也是最容易出錯的就是collection屬性,該屬性是必須指定的,但是在不同情況下,該屬性的值是不一樣的,主要有以下3種情況:
如果傳入的是單參數(shù)且參數(shù)類型是一個List的時候,collection屬性值為list 如果傳入的是單參數(shù)且參數(shù)類型是一個array數(shù)組的時候,collection的屬性值為array 如果傳入的參數(shù)是多個的時候,我們就需要把它們封裝成一個Map了,當(dāng)然單參數(shù)也可以封裝成map,實際上如果你在傳入?yún)?shù)的時候,在MyBatis里面也是會把它封裝成一個Map的,
map的key就是參數(shù)名,所以這個時候collection屬性值就是傳入的List或array對象在自己封裝的map里面的key
看看批量保存的兩種用法:
<!-- 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>
<!-- 這種方式需要數(shù)據(jù)庫連接屬性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內(nèi)置的ExecutorType有3種,默認(rèn)為simple,該模式下它為每個語句的執(zhí)行創(chuàng)建一個新的預(yù)處理語句,單條提交sql;而batch模式重復(fù)使用已經(jīng)預(yù)處理的語句,并且批量執(zhí)行所有更新語句,顯然batch性能將更優(yōu);但batch模式也有自己的問題,比如在Insert操作時,在事務(wù)沒有提交之前,是沒有辦法獲取到自增的id,在某些情況下不符合業(yè)務(wù)的需求。
具體用法如下:
//批量保存方法測試
@Test
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以執(zhí)行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
//批量保存執(zhí)行前時間
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();
//批量保存執(zhí)行后的時間
System.out.println("執(zhí)行時長" + (end - start));
//批量 預(yù)編譯sql一次==》設(shè)置參數(shù)==》10000次==》執(zhí)行1次 677
//非批量 (預(yù)編譯=設(shè)置參數(shù)=執(zhí)行 )==》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>
14.說說Mybatis的一級、二級緩存?
一級緩存: 基于 PerpetualCache 的 HashMap 本地緩存,其存儲作用域為SqlSession,各個SqlSession之間的緩存相互隔離,當(dāng) Session flush 或 close 之后,該 SqlSession 中的所有 Cache 就將清空,MyBatis默認(rèn)打開一級緩存。

Mybatis一級緩存 二級緩存與一級緩存其機(jī)制相同,默認(rèn)也是采用 PerpetualCache,HashMap 存儲,不同之處在于其存儲作用域為 Mapper(Namespace),可以在多個SqlSession之間共享,并且可自定義存儲源,如 Ehcache。默認(rèn)不打開二級緩存,要開啟二級緩存,使用二級緩存屬性類需要實現(xiàn)Serializable序列化接口(可用來保存對象的狀態(tài)),可在它的映射文件中配置。

Mybatis二級緩存示意圖 原理
15.能說說MyBatis的工作原理嗎?
我們已經(jīng)大概知道了MyBatis的工作流程,按工作原理,可以分為兩大步:生成會話工廠、會話運行。

MyBatis的工作流程 MyBatis是一個成熟的框架,篇幅限制,這里抓大放小,來看看它的主要工作流程。
構(gòu)建會話工廠
構(gòu)造會話工廠也可以分為兩步:

構(gòu)建會話工廠 獲取配置
獲取配置這一步經(jīng)過了幾步轉(zhuǎn)化,最終由生成了一個配置類Configuration實例,這個配置類實例非常重要,主要作用包括:
讀取配置文件,包括基礎(chǔ)配置文件和映射文件
初始化基礎(chǔ)配置,比如MyBatis的別名,還有其它的一些重要的類對象,像插件、映射器、ObjectFactory等等
提供一個單例,作為會話工廠構(gòu)建的重要參數(shù)
它的構(gòu)建過程也會初始化一些環(huán)境變量,比如數(shù)據(jù)源
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
//省略異常處理
//xml配置構(gòu)建器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//通過轉(zhuǎn)化的Configuration構(gòu)建SqlSessionFactory
var5 = this.build(parser.parse());
}
構(gòu)建SqlSessionFactory
SqlSessionFactory只是一個接口,構(gòu)建出來的實際上是它的實現(xiàn)類的實例,一般我們用的都是它的實現(xiàn)類DefaultSqlSessionFactory,
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
會話運行
會話運行是MyBatis最復(fù)雜的部分,它的運行離不開四大組件的配合:

MyBatis會話運行四大關(guān)鍵組件 Executor(執(zhí)行器)
Executor起到了至關(guān)重要的作用,SqlSession只是一個門面,相當(dāng)于客服,真正干活的是是Executor,就像是默默無聞的工程師。它提供了相應(yīng)的查詢和更新方法,以及事務(wù)方法。
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//通過Configuration創(chuàng)建executor
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
StatementHandler(數(shù)據(jù)庫會話器)
StatementHandler,顧名思義,處理數(shù)據(jù)庫會話的。我們以SimpleExecutor為例,看一下它的查詢方法,先生成了一個StatementHandler實例,再拿這個handler去執(zhí)行query。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
再以最常用的PreparedStatementHandler看一下它的query方法,其實在上面的prepareStatement已經(jīng)對參數(shù)進(jìn)行了預(yù)編譯處理,到了這里,就直接執(zhí)行sql,使用ResultHandler處理返回結(jié)果。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
ParameterHandler (參數(shù)處理器)
PreparedStatementHandler里對sql進(jìn)行了預(yù)編譯處理
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}
這里用的就是ParameterHandler,setParameters的作用就是設(shè)置預(yù)編譯SQL語句的參數(shù)。
里面還會用到typeHandler類型處理器,對類型進(jìn)行處理。
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement var1) throws SQLException;
}
ResultSetHandler(結(jié)果處理器)
我們前面也看到了,最后的結(jié)果要通過ResultSetHandler來進(jìn)行處理,handleResultSets這個方法就是用來包裝結(jié)果集的。Mybatis為我們提供了一個DefaultResultSetHandler,通常都是用這個實現(xiàn)類去進(jìn)行結(jié)果的處理的。
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement var1) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
void handleOutputParameters(CallableStatement var1) throws SQLException;
}
它會使用typeHandle處理類型,然后用ObjectFactory提供的規(guī)則組裝對象,返回給調(diào)用者。
整體上總結(jié)一下會話運行:

會話運行的簡單示意圖 PS:以上源碼分析比較簡單,在真正的源碼大佬面前可能過不了關(guān),有條件的建議Debug一下MyBatis的源碼。
我們最后把整個的工作流程串聯(lián)起來,簡單總結(jié)一下:

MyBatis整體工作原理圖 讀取 MyBatis 配置文件——mybatis-config.xml 、加載映射文件——映射文件即 SQL 映射文件,文件中配置了操作數(shù)據(jù)庫的 SQL 語句。最后生成一個配置對象。
構(gòu)造會話工廠:通過 MyBatis 的環(huán)境等配置信息構(gòu)建會話工廠 SqlSessionFactory。
創(chuàng)建會話對象:由會話工廠創(chuàng)建 SqlSession 對象,該對象中包含了執(zhí)行 SQL 語句的所有方法。
Executor 執(zhí)行器:MyBatis 底層定義了一個 Executor 接口來操作數(shù)據(jù)庫,它將根據(jù) SqlSession 傳遞的參數(shù)動態(tài)地生成需要執(zhí)行的 SQL 語句,同時負(fù)責(zé)查詢緩存的維護(hù)。
StatementHandler:數(shù)據(jù)庫會話器,串聯(lián)起參數(shù)映射的處理和運行結(jié)果映射的處理。
參數(shù)處理:對輸入?yún)?shù)的類型進(jìn)行處理,并預(yù)編譯。
結(jié)果處理:對返回結(jié)果的類型進(jìn)行處理,根據(jù)對象映射規(guī)則,返回相應(yīng)的對象。
16.MyBatis的功能架構(gòu)是什么樣的?

MyBatis功能架構(gòu) 我們一般把Mybatis的功能架構(gòu)分為三層:
API接口層:提供給外部使用的接口API,開發(fā)人員通過這些本地API來操縱數(shù)據(jù)庫。接口層一接收到調(diào)用請求就會調(diào)用數(shù)據(jù)處理層來完成具體的數(shù)據(jù)處理。 數(shù)據(jù)處理層:負(fù)責(zé)具體的SQL查找、SQL解析、SQL執(zhí)行和執(zhí)行結(jié)果映射處理等。它主要的目的是根據(jù)調(diào)用的請求完成一次數(shù)據(jù)庫操作。 基礎(chǔ)支撐層:負(fù)責(zé)最基礎(chǔ)的功能支撐,包括連接管理、事務(wù)管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來作為最基礎(chǔ)的組件。為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐。
17.為什么Mapper接口不需要實現(xiàn)類?
四個字回答:動態(tài)代理,我們來看一下獲取Mapper的過程:

Mapper代理 獲取Mapper
我們都知道定義的Mapper接口是沒有實現(xiàn)類的,Mapper映射其實是通過動態(tài)代理實現(xiàn)的。
BlogMapper mapper = session.getMapper(BlogMapper.class);
七拐八繞地進(jìn)去看一下,發(fā)現(xiàn)獲取Mapper的過程,需要先獲取MapperProxyFactory——Mapper代理工廠。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
……
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
這里可以看到動態(tài)代理對接口的綁定,它的作用就是生成動態(tài)代理對象(占位),而代理的方法被放到了MapperProxy中。
MapperProxy里,通常會生成一個MapperMethod對象,它是通過cachedMapperMethod方法對其進(jìn)行初始化的,然后執(zhí)行excute方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
MapperMethod
MapperMethod里的excute方法,會真正去執(zhí)行sql。這里用到了命令模式,其實繞一圈,最終它還是通過SqlSession的實例去運行對象的sql。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
……
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
……
}
MapperProxy MapperProxyFactory
MapperProxyFactory的作用是生成MapperProxy(Mapper代理對象)。
18.Mybatis都有哪些Executor執(zhí)行器?

Mybatis Executor類型 Mybatis有三種基本的Executor執(zhí)行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
SimpleExecutor:每執(zhí)行一次update或select,就開啟一個Statement對象,用完立刻關(guān)閉Statement對象。 ReuseExecutor:執(zhí)行update或select,以sql作為key查找Statement對象,存在就使用,不存在就創(chuàng)建,用完后,不關(guān)閉Statement對象,而是放置于Map<String, Statement>內(nèi),供下一次使用。簡言之,就是重復(fù)使用Statement對象。 BatchExecutor:執(zhí)行update(沒有select,JDBC批處理不支持select),將所有sql都添加到批處理中(addBatch()),等待統(tǒng)一執(zhí)行(executeBatch()),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢后,等待逐一執(zhí)行executeBatch()批處理。與JDBC批處理相同。
作用范圍:Executor的這些特點,都嚴(yán)格限制在SqlSession生命周期范圍內(nèi)。
Mybatis中如何指定使用哪一種Executor執(zhí)行器?
在Mybatis配置文件中,在設(shè)置(settings)可以指定默認(rèn)的ExecutorType執(zhí)行器類型,也可以手動給DefaultSqlSessionFactory的創(chuàng)建SqlSession的方法傳遞ExecutorType類型參數(shù),如SqlSession openSession(ExecutorType execType)。 配置默認(rèn)的執(zhí)行器。SIMPLE 就是普通的執(zhí)行器;REUSE 執(zhí)行器會重用預(yù)處理語句(prepared statements);BATCH 執(zhí)行器將重用語句并執(zhí)行批量更新。
插件
19.說說Mybatis的插件運行原理,如何編寫一個插件?
插件的運行原理?
Mybatis會話的運行需要ParameterHandler、ResultSetHandler、StatementHandler、Executor這四大對象的配合,插件的原理就是在這四大對象調(diào)度的時候,插入一些我我們自己的代碼。

MyBatis插件原理簡圖 Mybatis使用JDK的動態(tài)代理,為目標(biāo)對象生成代理對象。它提供了一個工具類Plugin,實現(xiàn)了InvocationHandler接口。

Plugin中調(diào)用插件方法 使用Plugin生成代理對象,代理對象在調(diào)用方法的時候,就會進(jìn)入invoke方法,在invoke方法中,如果存在簽名的攔截方法,插件的intercept方法就會在這里被我們調(diào)用,然后就返回結(jié)果。如果不存在簽名方法,那么將直接反射調(diào)用我們要執(zhí)行的方法。
如何編寫一個插件?
我們自己編寫MyBatis 插件,只需要實現(xiàn)攔截器接口 Interceptor (org.apache.ibatis. plugin Interceptor ),在實現(xiàn)類中對攔截對象和方法進(jìn)行處理。
實現(xiàn)Mybatis的Interceptor接口并重寫intercept()方法
這里我們只是在目標(biāo)對象執(zhí)行目標(biāo)方法的前后進(jìn)行了打??;
public class MyInterceptor implements Interceptor {
Properties props=null;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before……");
//如果當(dāng)前代理的是一個非代理對象,那么就會調(diào)用真實攔截對象的方法
// 如果不是它就會調(diào)用下個插件代理對象的invoke方法
Object obj=invocation.proceed();
System.out.println("after……");
return obj;
}
}
然后再給插件編寫注解,確定要攔截的對象,要攔截的方法
@Intercepts({@Signature(
type = Executor.class, //確定要攔截的對象
method = "update", //確定要攔截的方法
args = {MappedStatement.class,Object.class} //攔截方法的參數(shù)
)})
public class MyInterceptor implements Interceptor {
Properties props=null;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before……");
//如果當(dāng)前代理的是一個非代理對象,那么就會調(diào)用真實攔截對象的方法
// 如果不是它就會調(diào)用下個插件代理對象的invoke方法
Object obj=invocation.proceed();
System.out.println("after……");
return obj;
}
}
最后,再MyBatis配置文件里面配置插件
<plugins>
<plugin interceptor="xxx.MyPlugin">
<property name="dbType",value="mysql"/>
</plugin>
</plugins>
20.MyBatis是如何進(jìn)行分頁的?分頁插件的原理是什么?
MyBatis是如何分頁的?
MyBatis使用RowBounds對象進(jìn)行分頁,它是針對ResultSet結(jié)果集執(zhí)行的內(nèi)存分頁,而非物理分頁??梢栽趕ql內(nèi)直接書寫帶有物理分頁的參數(shù)來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。
分頁插件的原理是什么?
分頁插件的基本原理是使用Mybatis提供的插件接口,實現(xiàn)自定義插件,攔截Executor的query方法 在執(zhí)行查詢的時候,攔截待執(zhí)行的sql,然后重寫sql,根據(jù)dialect方言,添加對應(yīng)的物理分頁語句和物理分頁參數(shù)。 舉例:select * from student,攔截sql后重寫為:select t.* from (select * from student) t limit 0, 10
可以看一下一個大概的MyBatis通用分頁攔截器:

Mybatis-通用分頁攔截器
參考[1]. MyBatis面試題(2020最新版:https://blog.csdn.net/ThinkWon/article/details/101292950)
[2].mybatis官網(wǎng):https://mybatis.org/mybatis-3/zh/index.html
[3].《深入淺出MyBatis基礎(chǔ)原理與實戰(zhàn)》
[4].聊聊MyBatis緩存機(jī)制:https://tech.meituan.com/2018/01/19/mybatis-cache.html
[5].《MyBatis從入門到精通》
瀏覽
69
大家好,今天我們的主角是MyBatis,作為當(dāng)前國內(nèi)最流行的ORM框架,是我們這些crud選手最趁手的工具,趕緊來看看面試都會問哪些問題吧。
基礎(chǔ)
1.說說什么是MyBatis?

先吹一下:
Mybatis 是一個半 ORM(對象關(guān)系映射)框架,它內(nèi)部封裝了 JDBC,開發(fā)時只需要關(guān)注 SQL 語句本身,不需要花費精力去處理加載驅(qū)動、創(chuàng)建連接、創(chuàng)建statement 等繁雜的過程。程序員直接編寫原生態(tài) sql,可以嚴(yán)格控制 sql 執(zhí)行性能,靈活度高。
MyBatis 可以使用 XML 或注解來配置和映射原生信息,將 POJO 映射成數(shù)據(jù)庫中的記錄,避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。
再說一下缺點
SQL語句的編寫工作量較大,尤其當(dāng)字段多、關(guān)聯(lián)表多時,對開發(fā)人員編寫SQL語句的功底有一定要求 SQL語句依賴于數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫移植性差,不能隨意更換數(shù)據(jù)庫
ORM是什么?

ORM(Object Relational Mapping),對象關(guān)系映射,是一種為了解決關(guān)系型數(shù)據(jù)庫數(shù)據(jù)與簡單Java對象(POJO)的映射關(guān)系的技術(shù)。簡單來說,ORM是通過使用描述對象和數(shù)據(jù)庫之間映射的元數(shù)據(jù),將程序中的對象自動持久化到關(guān)系型數(shù)據(jù)庫中。
為什么說Mybatis是半自動ORM映射工具?它與全自動的區(qū)別在哪里?
Hibernate屬于全自動ORM映射工具,使用Hibernate查詢關(guān)聯(lián)對象或者關(guān)聯(lián)集合對象時,可以根據(jù)對象關(guān)系模型直接獲取,所以它是全自動的。 而Mybatis在查詢關(guān)聯(lián)對象或關(guān)聯(lián)集合對象時,需要手動編寫SQL來完成,所以,被稱之為半自動ORM映射工具。
JDBC編程有哪些不足之處,MyBatis是如何解決的?

1、數(shù)據(jù)連接創(chuàng)建、釋放頻繁造成系統(tǒng)資源浪費從而影響系統(tǒng)性能 解決:在mybatis-config.xml中配置數(shù)據(jù)鏈接池,使用連接池統(tǒng)一管理數(shù)據(jù)庫連接。 2、sql語句寫在代碼中造成代碼不易維護(hù) 解決:將sql語句配置在XXXXmapper.xml文件中與java代碼分離。 3、向sql語句傳參數(shù)麻煩,因為sql語句的where條件不一定,可能多也可能少,占位符需要和參數(shù)一一對應(yīng)。 解決:Mybatis自動將java對象映射至sql語句。 4、對結(jié)果集解析麻煩,sql變化導(dǎo)致解析代碼變化,且解析前需要遍歷,如果能將數(shù)據(jù)庫記錄封裝成pojo對象解析比較方便。 解決:Mybatis自動將sql執(zhí)行結(jié)果映射至java對象。
2.Hibernate 和 MyBatis 有什么區(qū)別?
PS:直接用Hibernate的應(yīng)該不多了吧,畢竟大家都是“敏捷開發(fā)”,但架不住面試愛問。
相同點
都是對jdbc的封裝,都是應(yīng)用于持久層的框架。

不同點
映射關(guān)系
MyBatis 是一個半自動映射的框架,配置Java對象與sql語句執(zhí)行結(jié)果的對應(yīng)關(guān)系,多表關(guān)聯(lián)關(guān)系配置簡單 Hibernate 是一個全表映射的框架,配置Java對象與數(shù)據(jù)庫表的對應(yīng)關(guān)系,多表關(guān)聯(lián)關(guān)系配置復(fù)雜 SQL優(yōu)化和移植性
Hibernate 對SQL語句封裝,提供了日志、緩存、級聯(lián)(級聯(lián)比 MyBatis 強(qiáng)大)等特性,此外還提供 HQL(Hibernate Query Language)操作數(shù)據(jù)庫,數(shù)據(jù)庫無關(guān)性支持好,但會多消耗性能。如果項目需要支持多種數(shù)據(jù)庫,代碼開發(fā)量少,但SQL語句優(yōu)化困難。 MyBatis 需要手動編寫 SQL,支持動態(tài) SQL、處理列表、動態(tài)生成表名、支持存儲過程。開發(fā)工作量相對大些。直接使用SQL語句操作數(shù)據(jù)庫,不支持?jǐn)?shù)據(jù)庫無關(guān)性,但sql語句優(yōu)化容易。
MyBatis和Hibernate的適用場景?

Hibernate 是標(biāo)準(zhǔn)的ORM框架,SQL編寫量較少,但不夠靈活,適合于需求相對穩(wěn)定,中小型的軟件項目,比如:辦公自動化系統(tǒng)
MyBatis 是半ORM框架,需要編寫較多SQL,但是比較靈活,適合于需求變化頻繁,快速迭代的項目,比如:電商網(wǎng)站
3.MyBatis使用過程?生命周期?
MyBatis基本使用的過程大概可以分為這么幾步:

1、 創(chuàng)建SqlSessionFactory
可以從配置或者直接編碼來創(chuàng)建SqlSessionFactory
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2、 通過SqlSessionFactory創(chuàng)建SqlSession
SqlSession(會話)可以理解為程序和數(shù)據(jù)庫之間的橋梁
SqlSession session = sqlSessionFactory.openSession();
3、 通過sqlsession執(zhí)行數(shù)據(jù)庫操作
可以通過 SqlSession 實例來直接執(zhí)行已映射的 SQL 語句:
Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);更常用的方式是先獲取Mapper(映射),然后再執(zhí)行SQL語句:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);4、 調(diào)用session.commit()提交事務(wù)
如果是更新、刪除語句,我們還需要提交一下事務(wù)。
5、 調(diào)用session.close()關(guān)閉會話
最后一定要記得關(guān)閉會話。
MyBatis生命周期?
上面提到了幾個MyBatis的組件,一般說的MyBatis生命周期就是這些組件的生命周期。
SqlSessionFactoryBuilder
一旦創(chuàng)建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 實例的生命周期只存在于方法的內(nèi)部。
SqlSessionFactory
SqlSessionFactory 是用來創(chuàng)建SqlSession的,相當(dāng)于一個數(shù)據(jù)庫連接池,每次創(chuàng)建SqlSessionFactory都會使用數(shù)據(jù)庫資源,多次創(chuàng)建和銷毀是對資源的浪費。所以SqlSessionFactory是應(yīng)用級的生命周期,而且應(yīng)該是單例的。
SqlSession
SqlSession相當(dāng)于JDBC中的Connection,SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的生命周期是一次請求或一個方法。
Mapper
映射器是一些綁定映射語句的接口。映射器接口的實例是從 SqlSession 中獲得的,它的生命周期在sqlsession事務(wù)方法之內(nèi),一般會控制在方法級。

當(dāng)然,萬物皆可集成Spring,MyBatis通常也是和Spring集成使用,Spring可以幫助我們創(chuàng)建線程安全的、基于事務(wù)的 SqlSession 和映射器,并將它們直接注入到我們的 bean 中,我們不需要關(guān)心它們的創(chuàng)建過程和生命周期,那就是另外的故事了。
ps:接下來看看Mybatis的基本使用,不會有人不會吧?不會吧!

4.在mapper中如何傳遞多個參數(shù)?

方法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>
#{}里面的數(shù)字代表傳入?yún)?shù)的順序。 這種方法不建議使用,sql層表達(dá)不直觀,且一旦順序調(diào)整容易出錯。
方法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>
#{}里面的名稱對應(yīng)的是注解@Param括號里面修飾的名稱。 這種方法在參數(shù)不多的情況還是比較直觀的,(推薦使用)。
方法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>
#{}里面的名稱對應(yīng)的是Map里面的key名稱。 這種方法適合傳遞多個參數(shù),且參數(shù)易變能靈活傳遞的情況。
方法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>
#{}里面的名稱對應(yīng)的是User類里面的成員屬性。 這種方法直觀,需要建一個實體類,擴(kuò)展不容易,需要加屬性,但代碼可讀性強(qiáng),業(yè)務(wù)邏輯處理方便,推薦使用。(推薦使用)。
5.實體類屬性名和表中字段名不一樣 ,怎么辦?
第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 中的<result>來映射字段名和實體類屬性名的一一對應(yīng)的關(guān)系。
<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為數(shù)據(jù)庫表中的屬性–>
<result property ="orderno" column ="order_no"/>
<result property="price" column="order_price" />
</reslutMap>
6.Mybatis是否可以映射Enum枚舉類?
Mybatis當(dāng)然可以映射枚舉類,不單可以映射枚舉類,Mybatis可以映射任何對象到表的一列上。映射方式為自定義一個TypeHandler,實現(xiàn)TypeHandler的setParameter()和getResult()接口方法。 TypeHandler有兩個作用,一是完成從javaType至jdbcType的轉(zhuǎn)換,二是完成jdbcType至javaType的轉(zhuǎn)換,體現(xiàn)為setParameter()和getResult()兩個方法,分別代表設(shè)置sql問號占位符參數(shù)和獲取列查詢結(jié)果。
7.#{}和${}的區(qū)別?

#{}是占位符,預(yù)編譯處理;${}是拼接符,字符串替換,沒有預(yù)編譯處理。 Mybatis在處理#{}時,#{}傳入?yún)?shù)是以字符串傳入,會將SQL中的#{}替換為?號,調(diào)用PreparedStatement的set方法來賦值。 #{} 可以有效的防止SQL注入,提高系統(tǒng)安全性;${} 不能防止SQL 注入 #{} 的變量替換是在DBMS 中;${} 的變量替換是在 DBMS 外
8.模糊查詢like語句該怎么寫?
1 ’%${question}%’ 可能引起SQL注入,不推薦 2 "%"#{question}"%" 注意:因為#{…}解析成sql語句時候,會在變量外側(cè)自動加單引號’ ',所以這里 % 需要使用雙引號" ",不能使用單引號 ’ ',不然會查不到任何結(jié)果。 3 CONCAT(’%’,#{question},’%’) 使用CONCAT()函數(shù),(推薦?) 4 使用bind標(biāo)簽(不推薦)
<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>
9.Mybatis能執(zhí)行一對一、一對多的關(guān)聯(lián)查詢嗎?
當(dāng)然可以,不止支持一對一、一對多的關(guān)聯(lián)查詢,還支持多對多、多對一的關(guān)聯(lián)查詢。

一對一<association>
比如訂單和支付是一對一的關(guān)系,這種關(guān)聯(lián)的實現(xiàn):
實體類:
public class Order {
private Integer orderId;
private String orderDesc;
/**
* 支付對象
*/
private Pay pay;
//……
}結(jié)果映射
<!-- 訂單resultMap -->
<resultMap id="peopleResultMap" type="cn.fighter3.entity.Order">
<id property="orderId" column="order_id" />
<result property="orderDesc" column="order_desc"/>
<!--一對一結(jié)果映射-->
<association property="pay" javaType="cn.fighter3.entity.Pay">
<id column="payId" property="pay_id"/>
<result column="account" property="account"/>
</association>
</resultMap>查詢就是普通的關(guān)聯(lián)查
<select id="getTeacher" resultMap="getTeacherMap" parameterType="int">
select * from order o
left join pay p on o.order_id=p.order_id
where o.order_id=#{orderId}
</select>一對多<collection>
比如商品分類和商品,是一對多的關(guān)系。
查詢
查詢就是一個普通的關(guān)聯(lián)查詢
<!-- 關(guān)聯(lián)查詢分類和產(chǎn)品表 -->
<select id="listCategory" resultMap="categoryBean">
select c.*, p.* from category_ c left join product_ p on c.id = p.cid
</select>實體類
public class Category {
private int categoryId;
private String categoryName;
/**
* 商品列表
**/
List<Product> products;
//……
}結(jié)果映射
<resultMap type="Category" id="categoryBean">
<id column="categoryId" property="category_id" />
<result column="categoryName" property="category_name" />
<!-- 一對多的關(guān)系 -->
<!-- property: 指的是集合屬性的值, ofType:指的是集合中元素的類型 -->
<collection property="products" ofType="Product">
<id column="product_id" property="productId" />
<result column="productName" property="productName" />
<result column="price" property="price" />
</collection>
</resultMap>
那么多對一、多對多怎么實現(xiàn)呢?還是利用<association>和<collection>,篇幅所限,這里就不展開了。
10.Mybatis是否支持延遲加載?原理?
Mybatis支持association關(guān)聯(lián)對象和collection關(guān)聯(lián)集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啟用延遲加載lazyLoadingEnabled=true|false。 它的原理是,使用CGLIB創(chuàng)建目標(biāo)對象的代理對象,當(dāng)調(diào)用目標(biāo)方法時,進(jìn)入攔截器方法,比如調(diào)用a.getB().getName(),攔截器invoke()方法發(fā)現(xiàn)a.getB()是null值,那么就會單獨發(fā)送事先保存好的查詢關(guān)聯(lián)B對象的sql,把B查詢上來,然后調(diào)用a.setB(b),于是a的對象b屬性就有值了,接著完成a.getB().getName()方法的調(diào)用。這就是延遲加載的基本原理。 當(dāng)然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。
11.如何獲取生成的主鍵?
新增標(biāo)簽中添加:keyProperty=" ID " 即可
<insert id="insert" useGeneratedKeys="true" keyProperty="userId" >
insert into user(
user_name, user_password, create_time)
values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>這時候就可以完成回填主鍵
mapper.insert(user);
user.getId;
12.MyBatis支持動態(tài)SQL嗎?
MyBatis中有一些支持動態(tài)SQL的標(biāo)簽,它們的原理是使用OGNL從SQL參數(shù)對象中計算表達(dá)式的值,根據(jù)表達(dá)式的值動態(tài)拼接SQL,以此來完成動態(tài)SQL的功能。

第一種方法:使用foreach標(biāo)簽
foreach的主要用在構(gòu)建in條件中,它可以在SQL語句中進(jìn)行迭代一個集合。foreach標(biāo)簽的屬性主要有item,index,collection,open,separator,close。
item?? 表示集合中每一個元素進(jìn)行迭代時的別名,隨便起的變量名; index?? 指定一個名字,用于表示在迭代過程中,每次迭代到的位置,不常用; open?? 表示該語句以什么開始,常用“(”; separator 表示在每次進(jìn)行迭代之間以什么符號作為分隔符,常用“,”; close?? 表示以什么結(jié)束,常用“)”。
在使用foreach的時候最關(guān)鍵的也是最容易出錯的就是collection屬性,該屬性是必須指定的,但是在不同情況下,該屬性的值是不一樣的,主要有以下3種情況:
如果傳入的是單參數(shù)且參數(shù)類型是一個List的時候,collection屬性值為list 如果傳入的是單參數(shù)且參數(shù)類型是一個array數(shù)組的時候,collection的屬性值為array 如果傳入的參數(shù)是多個的時候,我們就需要把它們封裝成一個Map了,當(dāng)然單參數(shù)也可以封裝成map,實際上如果你在傳入?yún)?shù)的時候,在MyBatis里面也是會把它封裝成一個Map的, map的key就是參數(shù)名,所以這個時候collection屬性值就是傳入的List或array對象在自己封裝的map里面的key
看看批量保存的兩種用法:
<!-- 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>
<!-- 這種方式需要數(shù)據(jù)庫連接屬性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內(nèi)置的ExecutorType有3種,默認(rèn)為simple,該模式下它為每個語句的執(zhí)行創(chuàng)建一個新的預(yù)處理語句,單條提交sql;而batch模式重復(fù)使用已經(jīng)預(yù)處理的語句,并且批量執(zhí)行所有更新語句,顯然batch性能將更優(yōu);但batch模式也有自己的問題,比如在Insert操作時,在事務(wù)沒有提交之前,是沒有辦法獲取到自增的id,在某些情況下不符合業(yè)務(wù)的需求。
具體用法如下:
//批量保存方法測試
@Test
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以執(zhí)行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
//批量保存執(zhí)行前時間
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();
//批量保存執(zhí)行后的時間
System.out.println("執(zhí)行時長" + (end - start));
//批量 預(yù)編譯sql一次==》設(shè)置參數(shù)==》10000次==》執(zhí)行1次 677
//非批量 (預(yù)編譯=設(shè)置參數(shù)=執(zhí)行 )==》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>
14.說說Mybatis的一級、二級緩存?
一級緩存: 基于 PerpetualCache 的 HashMap 本地緩存,其存儲作用域為SqlSession,各個SqlSession之間的緩存相互隔離,當(dāng) Session flush 或 close 之后,該 SqlSession 中的所有 Cache 就將清空,MyBatis默認(rèn)打開一級緩存。

Mybatis一級緩存 二級緩存與一級緩存其機(jī)制相同,默認(rèn)也是采用 PerpetualCache,HashMap 存儲,不同之處在于其存儲作用域為 Mapper(Namespace),可以在多個SqlSession之間共享,并且可自定義存儲源,如 Ehcache。默認(rèn)不打開二級緩存,要開啟二級緩存,使用二級緩存屬性類需要實現(xiàn)Serializable序列化接口(可用來保存對象的狀態(tài)),可在它的映射文件中配置。

原理
15.能說說MyBatis的工作原理嗎?
我們已經(jīng)大概知道了MyBatis的工作流程,按工作原理,可以分為兩大步:生成會話工廠、會話運行。

MyBatis是一個成熟的框架,篇幅限制,這里抓大放小,來看看它的主要工作流程。
構(gòu)建會話工廠
構(gòu)造會話工廠也可以分為兩步:

獲取配置
獲取配置這一步經(jīng)過了幾步轉(zhuǎn)化,最終由生成了一個配置類Configuration實例,這個配置類實例非常重要,主要作用包括:
讀取配置文件,包括基礎(chǔ)配置文件和映射文件
初始化基礎(chǔ)配置,比如MyBatis的別名,還有其它的一些重要的類對象,像插件、映射器、ObjectFactory等等
提供一個單例,作為會話工廠構(gòu)建的重要參數(shù)
它的構(gòu)建過程也會初始化一些環(huán)境變量,比如數(shù)據(jù)源
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
//省略異常處理
//xml配置構(gòu)建器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//通過轉(zhuǎn)化的Configuration構(gòu)建SqlSessionFactory
var5 = this.build(parser.parse());
}構(gòu)建SqlSessionFactory
SqlSessionFactory只是一個接口,構(gòu)建出來的實際上是它的實現(xiàn)類的實例,一般我們用的都是它的實現(xiàn)類DefaultSqlSessionFactory,
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
會話運行
會話運行是MyBatis最復(fù)雜的部分,它的運行離不開四大組件的配合:

Executor(執(zhí)行器)
Executor起到了至關(guān)重要的作用,SqlSession只是一個門面,相當(dāng)于客服,真正干活的是是Executor,就像是默默無聞的工程師。它提供了相應(yīng)的查詢和更新方法,以及事務(wù)方法。
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//通過Configuration創(chuàng)建executor
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);StatementHandler(數(shù)據(jù)庫會話器)
StatementHandler,顧名思義,處理數(shù)據(jù)庫會話的。我們以SimpleExecutor為例,看一下它的查詢方法,先生成了一個StatementHandler實例,再拿這個handler去執(zhí)行query。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}再以最常用的PreparedStatementHandler看一下它的query方法,其實在上面的
prepareStatement已經(jīng)對參數(shù)進(jìn)行了預(yù)編譯處理,到了這里,就直接執(zhí)行sql,使用ResultHandler處理返回結(jié)果。public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}ParameterHandler (參數(shù)處理器)
PreparedStatementHandler里對sql進(jìn)行了預(yù)編譯處理
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}這里用的就是ParameterHandler,setParameters的作用就是設(shè)置預(yù)編譯SQL語句的參數(shù)。
里面還會用到typeHandler類型處理器,對類型進(jìn)行處理。
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement var1) throws SQLException;
}ResultSetHandler(結(jié)果處理器)
我們前面也看到了,最后的結(jié)果要通過ResultSetHandler來進(jìn)行處理,handleResultSets這個方法就是用來包裝結(jié)果集的。Mybatis為我們提供了一個DefaultResultSetHandler,通常都是用這個實現(xiàn)類去進(jìn)行結(jié)果的處理的。
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement var1) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
void handleOutputParameters(CallableStatement var1) throws SQLException;
}它會使用typeHandle處理類型,然后用ObjectFactory提供的規(guī)則組裝對象,返回給調(diào)用者。
整體上總結(jié)一下會話運行:

PS:以上源碼分析比較簡單,在真正的源碼大佬面前可能過不了關(guān),有條件的建議Debug一下MyBatis的源碼。
我們最后把整個的工作流程串聯(lián)起來,簡單總結(jié)一下:

讀取 MyBatis 配置文件——mybatis-config.xml 、加載映射文件——映射文件即 SQL 映射文件,文件中配置了操作數(shù)據(jù)庫的 SQL 語句。最后生成一個配置對象。
構(gòu)造會話工廠:通過 MyBatis 的環(huán)境等配置信息構(gòu)建會話工廠 SqlSessionFactory。
創(chuàng)建會話對象:由會話工廠創(chuàng)建 SqlSession 對象,該對象中包含了執(zhí)行 SQL 語句的所有方法。
Executor 執(zhí)行器:MyBatis 底層定義了一個 Executor 接口來操作數(shù)據(jù)庫,它將根據(jù) SqlSession 傳遞的參數(shù)動態(tài)地生成需要執(zhí)行的 SQL 語句,同時負(fù)責(zé)查詢緩存的維護(hù)。
StatementHandler:數(shù)據(jù)庫會話器,串聯(lián)起參數(shù)映射的處理和運行結(jié)果映射的處理。
參數(shù)處理:對輸入?yún)?shù)的類型進(jìn)行處理,并預(yù)編譯。
結(jié)果處理:對返回結(jié)果的類型進(jìn)行處理,根據(jù)對象映射規(guī)則,返回相應(yīng)的對象。
16.MyBatis的功能架構(gòu)是什么樣的?

我們一般把Mybatis的功能架構(gòu)分為三層:
API接口層:提供給外部使用的接口API,開發(fā)人員通過這些本地API來操縱數(shù)據(jù)庫。接口層一接收到調(diào)用請求就會調(diào)用數(shù)據(jù)處理層來完成具體的數(shù)據(jù)處理。 數(shù)據(jù)處理層:負(fù)責(zé)具體的SQL查找、SQL解析、SQL執(zhí)行和執(zhí)行結(jié)果映射處理等。它主要的目的是根據(jù)調(diào)用的請求完成一次數(shù)據(jù)庫操作。 基礎(chǔ)支撐層:負(fù)責(zé)最基礎(chǔ)的功能支撐,包括連接管理、事務(wù)管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來作為最基礎(chǔ)的組件。為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐。
17.為什么Mapper接口不需要實現(xiàn)類?
四個字回答:動態(tài)代理,我們來看一下獲取Mapper的過程:

獲取Mapper
我們都知道定義的Mapper接口是沒有實現(xiàn)類的,Mapper映射其實是通過動態(tài)代理實現(xiàn)的。
BlogMapper mapper = session.getMapper(BlogMapper.class);七拐八繞地進(jìn)去看一下,發(fā)現(xiàn)獲取Mapper的過程,需要先獲取MapperProxyFactory——Mapper代理工廠。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
……
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}這里可以看到動態(tài)代理對接口的綁定,它的作用就是生成動態(tài)代理對象(占位),而代理的方法被放到了MapperProxy中。
MapperProxy里,通常會生成一個MapperMethod對象,它是通過cachedMapperMethod方法對其進(jìn)行初始化的,然后執(zhí)行excute方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}MapperMethod
MapperMethod里的excute方法,會真正去執(zhí)行sql。這里用到了命令模式,其實繞一圈,最終它還是通過SqlSession的實例去運行對象的sql。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
……
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
……
}MapperProxy MapperProxyFactory
MapperProxyFactory的作用是生成MapperProxy(Mapper代理對象)。
18.Mybatis都有哪些Executor執(zhí)行器?

Mybatis有三種基本的Executor執(zhí)行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
SimpleExecutor:每執(zhí)行一次update或select,就開啟一個Statement對象,用完立刻關(guān)閉Statement對象。 ReuseExecutor:執(zhí)行update或select,以sql作為key查找Statement對象,存在就使用,不存在就創(chuàng)建,用完后,不關(guān)閉Statement對象,而是放置于Map<String, Statement>內(nèi),供下一次使用。簡言之,就是重復(fù)使用Statement對象。 BatchExecutor:執(zhí)行update(沒有select,JDBC批處理不支持select),將所有sql都添加到批處理中(addBatch()),等待統(tǒng)一執(zhí)行(executeBatch()),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢后,等待逐一執(zhí)行executeBatch()批處理。與JDBC批處理相同。
作用范圍:Executor的這些特點,都嚴(yán)格限制在SqlSession生命周期范圍內(nèi)。
Mybatis中如何指定使用哪一種Executor執(zhí)行器?
在Mybatis配置文件中,在設(shè)置(settings)可以指定默認(rèn)的ExecutorType執(zhí)行器類型,也可以手動給DefaultSqlSessionFactory的創(chuàng)建SqlSession的方法傳遞ExecutorType類型參數(shù),如SqlSession openSession(ExecutorType execType)。 配置默認(rèn)的執(zhí)行器。SIMPLE 就是普通的執(zhí)行器;REUSE 執(zhí)行器會重用預(yù)處理語句(prepared statements);BATCH 執(zhí)行器將重用語句并執(zhí)行批量更新。
插件
19.說說Mybatis的插件運行原理,如何編寫一個插件?
插件的運行原理?
Mybatis會話的運行需要ParameterHandler、ResultSetHandler、StatementHandler、Executor這四大對象的配合,插件的原理就是在這四大對象調(diào)度的時候,插入一些我我們自己的代碼。

Mybatis使用JDK的動態(tài)代理,為目標(biāo)對象生成代理對象。它提供了一個工具類Plugin,實現(xiàn)了InvocationHandler接口。

使用Plugin生成代理對象,代理對象在調(diào)用方法的時候,就會進(jìn)入invoke方法,在invoke方法中,如果存在簽名的攔截方法,插件的intercept方法就會在這里被我們調(diào)用,然后就返回結(jié)果。如果不存在簽名方法,那么將直接反射調(diào)用我們要執(zhí)行的方法。
如何編寫一個插件?
我們自己編寫MyBatis 插件,只需要實現(xiàn)攔截器接口 Interceptor (org.apache.ibatis. plugin Interceptor ),在實現(xiàn)類中對攔截對象和方法進(jìn)行處理。
實現(xiàn)Mybatis的Interceptor接口并重寫intercept()方法
這里我們只是在目標(biāo)對象執(zhí)行目標(biāo)方法的前后進(jìn)行了打??;
public class MyInterceptor implements Interceptor {
Properties props=null;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before……");
//如果當(dāng)前代理的是一個非代理對象,那么就會調(diào)用真實攔截對象的方法
// 如果不是它就會調(diào)用下個插件代理對象的invoke方法
Object obj=invocation.proceed();
System.out.println("after……");
return obj;
}
}然后再給插件編寫注解,確定要攔截的對象,要攔截的方法
@Intercepts({@Signature(
type = Executor.class, //確定要攔截的對象
method = "update", //確定要攔截的方法
args = {MappedStatement.class,Object.class} //攔截方法的參數(shù)
)})
public class MyInterceptor implements Interceptor {
Properties props=null;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before……");
//如果當(dāng)前代理的是一個非代理對象,那么就會調(diào)用真實攔截對象的方法
// 如果不是它就會調(diào)用下個插件代理對象的invoke方法
Object obj=invocation.proceed();
System.out.println("after……");
return obj;
}
}最后,再MyBatis配置文件里面配置插件
<plugins>
<plugin interceptor="xxx.MyPlugin">
<property name="dbType",value="mysql"/>
</plugin>
</plugins>
20.MyBatis是如何進(jìn)行分頁的?分頁插件的原理是什么?
MyBatis是如何分頁的?
MyBatis使用RowBounds對象進(jìn)行分頁,它是針對ResultSet結(jié)果集執(zhí)行的內(nèi)存分頁,而非物理分頁??梢栽趕ql內(nèi)直接書寫帶有物理分頁的參數(shù)來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。
分頁插件的原理是什么?
分頁插件的基本原理是使用Mybatis提供的插件接口,實現(xiàn)自定義插件,攔截Executor的query方法 在執(zhí)行查詢的時候,攔截待執(zhí)行的sql,然后重寫sql,根據(jù)dialect方言,添加對應(yīng)的物理分頁語句和物理分頁參數(shù)。 舉例:select * from student,攔截sql后重寫為:select t.* from (select * from student) t limit 0, 10
可以看一下一個大概的MyBatis通用分頁攔截器:

參考
[1]. MyBatis面試題(2020最新版:https://blog.csdn.net/ThinkWon/article/details/101292950)
[2].mybatis官網(wǎng):https://mybatis.org/mybatis-3/zh/index.html
[3].《深入淺出MyBatis基礎(chǔ)原理與實戰(zhàn)》
[4].聊聊MyBatis緩存機(jī)制:https://tech.meituan.com/2018/01/19/mybatis-cache.html
[5].《MyBatis從入門到精通》
