MyBatis框架面試題總結(面試必備)

寫在前面:鴿了兩天的面試題終於出來了,mybatis的面試題也不少,考研名師唐遲曾說過:學習的進步不再於你學了多少,而在於你做了多少的總結。
最後再次感謝ThinkWon大神的總結。
原文鏈接:https://blog.csdn.net/ThinkWon/article/details/101292950

文章目錄

1. MyBatis簡介

1.1 MyBatis是什麼?

MyBatis是一款優秀的持久層框架,使用Java編寫,它封裝了JDBC的很多細節,使開發者能夠只關注於SQL語句本身,無需關注註冊驅動,創建連接等繁瑣功能,它使用了ORM的思想實現了結果集的封裝。

1.2 ORM是什麼

Object Relational Mapping對象關係映射,是一種解決關係型數據庫數據和Java對象(POJO)的映射關係。簡單的說就是實體類對象和數據庫數據的各種對應關係,如對象的屬性字段對應數據庫表的字段名稱。

1.3 爲什麼說Mybatis是半自動ORM映射工具?它與全自動的區別在哪裏?

因爲MyBatis查詢關聯對象或關聯集合時,需要手動來編寫SQL,所以時半自動化ORM映射工具。
而Hibernate框架是屬於全自動化的ORM映射關係,查詢關聯對象或關聯集合時不需要手寫SQL,可以根據對象模型直接獲取,所以是屬於全自動的。

1.4 傳統JDBC開發存在的問題

  1. 頻繁創建數據庫連接,釋放容易造成系統資源的浪費,影響性能,可以用連接池來解決這一問題,但是JDBC又需要自己來實現連接池。
  2. SQL語句定義,參數設置,結果集處理存在硬編碼,實際開發中,可能SQL語句的變化比較大,一旦修改,就要修改Java代碼,系統便要重新編譯,重新發布,不好維護。
  3. 使用preparedStatement在佔有符的傳參中存在硬編碼,因爲SQL語句的where條件不一定,可能多也可能少,修改SQL還需要重新修改Java代碼,不易維護。
  4. 結果集中存在重複代碼,處理麻煩,如果可以映射成Java對象會比較方便。

1.5 JDBC編程有哪些不足之處,MyBatis是如何解決這些問題的?

針對以上的傳統JDBC開發存在的問題,MyBatis開發出瞭解決方案。

  1. 在mybtais的主配置文件中配置數據庫連接池,使用連接池管理數據庫連接。
  2. 將SQL語句配置在接口的配置文件中,與實際的Java接口代碼分離。
  3. MyBatis使用ORM思想自動將Java對象映射到SQL本身語句
  4. MyBatis自動將SQL語句執行結果映射到Java對象。

1.6 Mybatis優缺點

優點

1. 基於SQL語句的編程,相當的靈活,不會對應用程序或者數據庫現有設計造成任何影響,SQL語句編寫在XML中,解除SQL與程序代碼的耦合,提供XML標籤,便於統一管理,支持編寫動態SQL,支持可重用。
2. 與傳統的JDBC相比,消除了大量的冗餘代碼,不需要手動開關數據庫連接。
3. 很好的可以與各種數據庫兼容MySQL,Oracle..
4. 提供映射標籤,支持對象與數據庫的ORM字段關係映射,提供對象關係映射標籤,支持對象關係組件的維護。
5. 能夠和spring很好的集成。

缺點

1.SQL語句編寫工作量很大,尤其在與字段多,管理表多的情況下,需要開發人員具備一定的SQL功底。
2.SQL語句依賴於數據庫,導致數據庫移植性差,不能隨意換數據庫。

1.7 MyBatis框架適用場景

  1. 對性能要求很高的項目,或者說需求變化較多的項目,如互聯網電商項目。
  2. MyBatis專注於SQL語句本身,是一個足夠靈活的Dao層解決方案。

Hibernate 和 MyBatis 的區別

相同點
都是對JDBC的封裝,都是持久層框架,都用於dao層的開發
不同點

  • 映射關係
1.MyBatis是一個半自動化的ORM映射框架,配置Java對象和SQL語句的執行結果對應關係,多表關聯配置簡單。
2.Hibernate 是一個全自動化的ORM映射框架,配置Java對象與數據庫表的對應關係,多表關聯配置複雜
  • SQL優化和移植
1.Hibernate 提供數據庫的無關性,即可以切換任意的數據庫,即移植性好,但是消耗性能,如果項目需要支持多種數據庫,代碼開發量少,但是SQL的優化卻很複雜。
2. MyBatis需要手動編寫SQL,支持動態SQL,處理表生成表名等操作,代碼量相對大一點,不支持數據庫無關性,即移植性差,但是SQL優化容易。
  • 開發難易程度和學習成本
1. Hibernate 屬於重量級的框架,學習成本高,適用於需求相對穩定的中小型項目,如:辦公系統。
2. MyBatis 屬於輕量級框架,學習成本低,適用於需求變化頻繁的大型互聯網項目,如互聯網大型電商系統。

小結

  • MyBatis 是一個小巧,輕便,高效,簡單,直接的半自動化持久層框架。
  • Hibernate 是一個強大,方便,高效,複雜,間接的全自動化持久層框架。

2. MyBatis的解析和運行原理

2.1 MyBatis編程步驟是什麼樣的?

  1. 加載配置文件創建SqlSessionFactory
  2. 通過SqlSessionFactory創建SqlSession
  3. 通過SqlSessioni執行數據庫操作(創建dao層代理對象)
  4. 釋放資源

2.2 請說說MyBatis的工作原理

工作原理圖如下:
在這裏插入圖片描述

  1. 讀取MyBatis主配置文件:裏面包含了數據庫的基本配置信息,如數據庫連接信息。
  2. 加載接口映射文件,從MyBatis主配置文件中加載,一個主配置文件可以配置多個接口映射文件,每個文件對應一張表。
  3. 構建會話工廠SqlSessionFactory:通過配置信息構建SqlSessionFactory會話工廠。
  4. 構建會話對象SqlSession:裏面包含了執行SQL的所有方法。
  5. Executor 執行器:MyBatis底層定義了一個Executor 接口操作數據庫,它將根據SqlSession傳遞的參數動態生成需要執行的SQL語句,同時負責查詢緩存。
  6. MappedStatement 對象:在Executor 接口執行的方法參數中有一個MappedStatement類型的參數,該參數是對映射信息的封裝,用於存儲要映射的SQL語句的id,參數等。
  7. 輸入參數映射:輸入參數可以是List,Map集合類型,也可以是基本類型或POJO類型,輸入參數映射類似於JDBC的preparedStatement設置參數的過程。
  8. 輸出參數映射:輸出參數可以實List,Map集合類型,也可以是基本類型或POJO類型,輸出參數類似於JDBC對結果集的解析過程。

2.3 MyBatis的功能架構是怎樣的

在這裏插入圖片描述
Mybatis的功能架構分爲三層:

  • API接口層:提供外部使用的接口API,開發人員通過這些API來操作數據庫,接口層一般接收調用請求後,就要來調用數據處理層來完成具體的數據處理。
  • 數據處理層:負責具體的SQL查找,SQL解析,SQL執行和執行結果的映射處理等。它的主要目的是根據調用的請求完成一次數據庫的操作。
  • 框架基礎支撐層:負責最基礎的功能支撐,包括連接管理,事務管理,配置加載和緩存處理,這些都是公用的東西,我們把它抽取出來作爲最基礎的組件,爲上層數據處理層提供最基礎的支撐。

2.4 MyBatis的框架架構設計是怎麼樣的

在這裏插入圖片描述
從上圖可以發現,MyBatis的初始化從加載主配置文件開始,生成Configuration類

  1. 加載配置:可以從代碼的註解或xml中加載,將SQL的配置信息加載成一個個MappedStatement對象,(包括了傳參映射配置,執行SQL,結果集映射配置)存儲到內存中。
  2. SQL解析:當API接口層接收到調用請求後,會接收到傳入的SQL的id和傳入對象,(可以是Map或JavaBean對象),MyBatis會根據SQL的id找到相應的MapperStatement,然後根據傳入的參數對MapperStatement進行解析,解析後得到最終要執行的SQL語句和參數。
  3. SQL執行:將最終得到的SQL和參數拿到數據庫進行執行,得到操作數據庫的結果。
  4. 結果映射:將數據庫的查詢結果根據映射配置進行轉換,可以轉換成Map,JavaBean或基本數據類型,並最終返回結果。

2.5 爲什麼需要預編譯

預編譯定義:
SQL預編譯是指數據庫驅動在發送SQL語句和參數之前對SQL語句進行編譯,這樣在DBMS執行SQL時進不需要進行編譯了。
爲什麼要預編譯?
JDBC通過preparedStatement對象來抽取預編譯的SQL語句,使用預編譯可以優化SQL的執行。預編譯之後的SQL語句可以直接執行,DBMS不需要再次編譯。提高了執行SQL的性能。同時預編譯對象可以重複使用,例如:把一個SQL預編譯後產生的preparedStatement存儲起來,下一次如果再次使用同一個SQL,可以直接使用這個緩存的PreparedStatement對象。再則就是防止SQL注入的問題,MyBatis默認情況下,將所有的SQL語句,進行預編譯。

深入理解:https://blog.csdn.net/JAYU_37/article/details/105444736

2.6 Mybatis是否支持延遲加載?如果支持,它的實現原理是什麼?

延遲加載的基本原理:
使用CGLIB創建目標的代理對象,當調用方法時,進入攔截器方法,比如調用a.getB().getName(),攔截器發現a.getB()是null值,就會單獨事先保存好的關聯B對象的sql,把B查詢上來,a.setB(b),之後b就有值了,接着完成a.getB().getName()方法的調用,這就是延遲加載的基本原理。

MyBatis是僅支持association關聯對象和collection關聯集合對象的延遲加載,association指的是一對一,collection指的是一對多的查詢。開啓MyBaits延遲加載需要在主配置文件中設置標籤< lazyloadingEnabled = true | false >

3. 映射器

3.1 #{}和${}的區別

  1. ${}傳入的值直接出現在SQL中,所以一般使用外部要加上 ‘’,而#{}會自動加上‘’
  2. #{}可以防止SQL注入問題,而${}不能,使用#{}傳參更加安全
  3. #{}的變量替換是在DBMS內,${}的變量替換是在DBMS外部
  4. #{}傳入的值會經過預編譯處理生成 ? 佔位符,調用preparedStatement的set方法爲其賦值
  5. 一般能使用#{}的地方儘量不要使用${}

參考:https://www.cnblogs.com/mww-NOTCOPY/p/11777187.html

3.2 模糊查詢like語句該怎麼寫

寫法一: ‘ %${param}%’ :可能會引起SQL注入問題,不推薦使用。
寫法二:"%"#{parm}"%"
注意:這種寫法極易出錯,因爲%後面的引號必須是”,因爲#{param}解析完之後會給自身套上一個‘’,例如:

SELECT * FROM USER WHERE NAME LIKE "%"'li'"%";   #可以查詢到,查詢的是li
SELECT * FROM USER WHERE NAME LIKE "%''li''%";   #不可以查詢到任何結果,查詢的是''li''

寫法三:concat(’%’,#{param},’%’);使用concat函數,推薦使用。
寫法四:使用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>

3.3 在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語句表達不直觀,一旦出錯很難發現錯誤。

  1. @Param註解傳參法
public User selectUser(@Param("userName")String name, @Param("deptId")int deptId);
<select id="selectUser" resultMap="UserResultMap">
	select * from user
	where 
	user_name= #{username} and dept_id=#{deptId};
</select>

#{}裏面的名稱是註解@Param()對應value名稱,使用於參數不多的情況下,此方法推薦使用。
3. Map傳參法

public User selectUser(Map<String,Object>params);
<select id="selectUser" resultMap="UserResultMap" parameterType="java.util.Map">
	select * from user
	where 
	user_name= #{username} and dept_id=#{deptId};
</select>

#{}裏面的名稱對應的是Map的key值,此方法使用與傳遞多個參數的情況下。
4. JavaBean傳參法

public User selectUser(User user);
<select id="selectUser" resultMap="UserResultMap" parameterType="com.jourwon.pojo.User">
	select * from user
	where 
	user_name= #{username} and dept_id=#{deptId};
</select>

#{}裏面的名稱傳的是User類對應的屬性名稱擴展不易,代碼可讀性強,業務邏輯處理方便,推薦使用。

3.4 Mybatis如何執行批量操作

使用foreach標籤
foreach主要用於構建in條件中,它可以在SQL語句中迭代一個集合,foreach的標籤屬性有item,index,collection,open,separator,close。

  • item:表示從集合中隨便去的一個別名,但最好能見名知意。
  • indx:用於指定一個名字,用於表示迭代過程中,每次迭代到的位置,並不常用。
  • open:什麼時候開始,一般使用“(”
  • close:什麼時候結束,常用“)”
  • separator:每次迭代時,中間以什麼符號進行分割,常用“,”

注意:使用foreach最關鍵也是最容易出錯的地方應該就是在collection屬性了,該屬性必須是指定的,但是在不用情況下,該屬性的值是不一樣的。主要有三種情況:

  1. 如果傳入的單參數類型是一個List,collection的屬性值爲list
  2. 如果傳入的單參數類型是一個array數組,collection的屬性值爲array
  3. 不管傳入單參還是多參,都可以將其封裝成Map,參數可以是集合或者對象類型,collection屬性值就是這些參數對應的key

具體用法:

<!-- int add(@Param("emps") List<Employee> emps)
MySQL下的批量保存,可以使用foreach遍歷,mysql支持values(),(),...-->
<insert  id="add" paramType="java.util.List">
	insert into emp(ename,gender,email,did)values
	<foreach collection="emps" item="emp" seperator=",">
	(#{emp.ename},#{emp.gender},#{emp.email},#{emp.dept.id})
	</foreach>
</insert>

3.5 如何獲取生成的主鍵

對於主鍵自增長的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 不寫,因爲idea的mybatis插件可以自動識別出來傳入的參數類型,如果要想訪問主鍵,那麼parameterType的類型應當是Map或Java實體,獲取主鍵值可以通過key或者getXXX方法

3.6 當實體類中的屬性名和表中的字段名不一樣 ,Mybatis是如何將sql執行結果封裝爲目標對象並返回的?

假設實體類的屬性名:

private Integer uid;
private String uname;
private String ugrader;

數據庫表user字段名:

int(10) id;
varchar(32) name;
varchar(32) grader;

方案一:
取別名,例如

<select id="getUser" paramterType="int" resultType="com.liuzeyu.User">
	select id uid,name uname,ugrader where id = #{uid}
</select>

方案二:
resultMap標籤

<resultMap id="userMap" type="com.liuzeyu.User">
	<id property = "uid" column="id"/>
	<result property = "uname" column = "name"/>
	<result property = "ugrader" column="grander"/>
</resultMap>

<select id="getUser" paramterType="int" resultMap="userMap">
	select id uid,name uname,ugrader where id = #{uid}
</select>

3.7 Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重複?

不同的接口映射文件中如果配置了namespace則id可以重複,否則不能。
原因:

原因就是namespace+id是作爲Map<String, MappedStatement>的key使用的,
如果沒有namespace,就剩下id,那麼,id重複會導致數據互相覆蓋。
有了namespace,自然id就可以重複,namespace不同,namespace+id自然也就不同。

3.8 什麼是MyBatis的接口綁定?有哪些實現方式?

所謂的接口綁定,就是在mybatis中定義任意接口,然後把接口的方法和SQL語句綁定,我們直接調用接口方法就可以了,比前的使用sqlSession提供的方法更加的靈活。
方法1:使用註解綁定,就是在接口上使用@Select,@Delete,@Update…裏面編寫SQL語句
方法二:使用xml來綁定,在這種情況下,xml的接口映射配置文件的namespace屬性必須配置接口的全限定類名。
當SQL語句比較簡單時使用註解綁定,反之使用xml綁定

3.9 使用MyBatis的mapper接口調用時有哪些要求?

  1. Mapper接口方法名必須和mapperx.xml每個sql的id一致
  2. Mapper接口方法的參數類型必須和mapper.xml中每個sql的parameterType類型一致,可以取別名,只有一個參數時parameterType屬性可以不寫
  3. Mapper接口方法的返回參數類型必須和mapper.xml中每個sql的resultType類型一致
  4. mapper.xml的namespace必須是Mapper接口的全限定類名

3.10 Mapper 編寫有哪幾種方式?

總共有三種方案。

  1. 使用mapper掃描器
    1. mapper.xml文件編寫:
      mapper.xml中的namespace必須添加mapper接口的全限定類名
      mapper接口方法名必須和mapper.xml定義的執行sql語句id名稱一致
      如果mapper.xml和mapper接口的名稱保持一致則不用在mybatis主配置文件進行配置mapper.xml的具體位置,如下

      mappers>
          <mapper resource="mapper.xml 文件的地址" />
          <mapper resource="mapper.xml 文件的地址" />
      </mappers>
      
    2. 定義mapper接口
      注意mapper.xml的文件名必須和mapper接口名必須保持一致並且放在同一目錄下。

    3. 配置mapper掃描

      <bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer">
      	<property name="basePackage" value="mapper接口權限地類名"/>
      	<property name="SqlSessionFactoryBeanName" value="sessionFactory"/>
      </bean>
      
    4. 使用掃描器後可以從spring容器中獲取mapper的實現類對象。
      使用的是resultType

3.11 Xml映射文件中,除了常見的select|insert|updae|delete標籤之外,還有哪些標籤?

還有很多標籤,例如:

<resultMap>:結果集映射
<parameterMap>:參數映射
<sql>:sql片段標籤
<include>:引入sql片段標籤
<selectKey>:爲不自增的主鍵添加策略標籤
.....

3.12 Mybatis映射文件中,如果A標籤通過include引用了B標籤的內容,請問,B標籤能否定義在A標籤的後面,還是說必須定義在A標籤的前面?

A標籤引用了B標籤,雖然B標籤在A標籤後面,但是還是可以被正確引用。
原因:

因爲配置文件加載到A標籤後,發現了引入的B標籤內容還未被解析到,此時A標籤就置爲未解析狀態
繼續解析以下的標籤,包含B標籤,知道解析完B標籤,待所有的標籤都解析完之後,MyBatis就將
哪些未被解析的內容再次解析,所以A標籤也可以被正常解析。

4. 高級查詢

4.1 MyBatis實現一對一,一對多有幾種方式,怎麼操作的?

使用聯合查詢和嵌套查詢

  • 聯合查詢:幾張表聯合查詢,只查詢一次,通過resultMap的association和collection節點配置一對一,一對多查詢。
  • 嵌套查詢:先查一個表,根據這個表裏面的結果的外鍵id,去另外一個表裏面查詢,也是通過配置association和collection,但另外一張表的查詢需要用到select標籤。

5. 動態SQL

5.1 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的功能。

6. 緩存

6.1 簡述以下Mybatis的一級、二級緩存

一級緩存:一級緩存SqlSession對象範圍的緩存,當我們執行查詢之後,查詢結果會同時存入SqlSession爲我們提供的一塊區域中,該區域的結構是Map,當我們再次查詢到同樣的數據時mybatis會先從sqlSession中獲取。當調用SqlSession增刪改,commit(),close()時就會清空一級緩存。
二級緩存:二級緩存的機制和一級相同,它是指Mybatis的SqlSessionFactory對象緩存,在同一個SqlSessionFactory創建的SqlSession對象內共享緩存。默認不開啓二級緩存,若要開啓,存儲的類必須實現序列化Serializable接口,作用是用來保存對象狀態,並且在映射配置文件中開啓< cache/>

注意:緩存默認更新機制,當某一作用域(SqlSession/SqlSessionFactory)進行了G/U/D(增刪改)後,默認作用域下的select中的緩存將被清空。

寫在最後:
3.10處遇到一個問題:如果mapper.xml和mapper接口的名稱保持一致則不用在mybatis主配置文件進行配置mapper.xml的具體位置,但是在我項目中如果不配置於數據庫交互則會報錯?所以3.10的說法是否正確有待考察…

<mappers>
    <mapper resource="mapper.xml 文件的地址" />
    <mapper resource="mapper.xml 文件的地址" />
</mappers>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章