寫在前面:鴿了兩天的面試題終於出來了,mybatis的面試題也不少,考研名師唐遲曾說過:學習的進步不再於你學了多少,而在於你做了多少的總結。
最後再次感謝ThinkWon大神的總結。
原文鏈接:https://blog.csdn.net/ThinkWon/article/details/101292950
文章目錄
- 1. MyBatis簡介
- 1.1 MyBatis是什麼?
- 1.2 ORM是什麼
- 1.3 爲什麼說Mybatis是半自動ORM映射工具?它與全自動的區別在哪裏?
- 1.4 傳統JDBC開發存在的問題
- 1.5 JDBC編程有哪些不足之處,MyBatis是如何解決這些問題的?
- 1.6 Mybatis優缺點
- 1.7 MyBatis框架適用場景
- Hibernate 和 MyBatis 的區別
- 2. MyBatis的解析和運行原理
- 2.1 MyBatis編程步驟是什麼樣的?
- 2.2 請說說MyBatis的工作原理
- 2.3 MyBatis的功能架構是怎樣的
- 2.4 MyBatis的框架架構設計是怎麼樣的
- 2.5 爲什麼需要預編譯
- 2.6 Mybatis是否支持延遲加載?如果支持,它的實現原理是什麼?
- 3. 映射器
- 3.1 #{}和${}的區別
- 3.2 模糊查詢like語句該怎麼寫
- 3.3 在mapper中如何傳遞多個參數
- 3.4 Mybatis如何執行批量操作
- 3.5 如何獲取生成的主鍵
- 3.6 當實體類中的屬性名和表中的字段名不一樣 ,Mybatis是如何將sql執行結果封裝爲目標對象並返回的?
- 3.7 Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重複?
- 3.8 什麼是MyBatis的接口綁定?有哪些實現方式?
- 3.9 使用MyBatis的mapper接口調用時有哪些要求?
- 3.10 Mapper 編寫有哪幾種方式?
- 3.11 Xml映射文件中,除了常見的select|insert|updae|delete標籤之外,還有哪些標籤?
- 3.12 Mybatis映射文件中,如果A標籤通過include引用了B標籤的內容,請問,B標籤能否定義在A標籤的後面,還是說必須定義在A標籤的前面?
- 4. 高級查詢
- 5. 動態SQL
- 6. 緩存
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開發存在的問題
- 頻繁創建數據庫連接,釋放容易造成系統資源的浪費,影響性能,可以用連接池來解決這一問題,但是JDBC又需要自己來實現連接池。
- SQL語句定義,參數設置,結果集處理存在硬編碼,實際開發中,可能SQL語句的變化比較大,一旦修改,就要修改Java代碼,系統便要重新編譯,重新發布,不好維護。
- 使用preparedStatement在佔有符的傳參中存在硬編碼,因爲SQL語句的where條件不一定,可能多也可能少,修改SQL還需要重新修改Java代碼,不易維護。
- 結果集中存在重複代碼,處理麻煩,如果可以映射成Java對象會比較方便。
1.5 JDBC編程有哪些不足之處,MyBatis是如何解決這些問題的?
針對以上的傳統JDBC開發存在的問題,MyBatis開發出瞭解決方案。
- 在mybtais的主配置文件中配置數據庫連接池,使用連接池管理數據庫連接。
- 將SQL語句配置在接口的配置文件中,與實際的Java接口代碼分離。
- MyBatis使用ORM思想自動將Java對象映射到SQL本身語句
- 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框架適用場景
- 對性能要求很高的項目,或者說需求變化較多的項目,如互聯網電商項目。
- 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編程步驟是什麼樣的?
- 加載配置文件創建SqlSessionFactory
- 通過SqlSessionFactory創建SqlSession
- 通過SqlSessioni執行數據庫操作(創建dao層代理對象)
- 釋放資源
2.2 請說說MyBatis的工作原理
工作原理圖如下:
- 讀取MyBatis主配置文件:裏面包含了數據庫的基本配置信息,如數據庫連接信息。
- 加載接口映射文件,從MyBatis主配置文件中加載,一個主配置文件可以配置多個接口映射文件,每個文件對應一張表。
- 構建會話工廠SqlSessionFactory:通過配置信息構建SqlSessionFactory會話工廠。
- 構建會話對象SqlSession:裏面包含了執行SQL的所有方法。
- Executor 執行器:MyBatis底層定義了一個Executor 接口操作數據庫,它將根據SqlSession傳遞的參數動態生成需要執行的SQL語句,同時負責查詢緩存。
- MappedStatement 對象:在Executor 接口執行的方法參數中有一個MappedStatement類型的參數,該參數是對映射信息的封裝,用於存儲要映射的SQL語句的id,參數等。
- 輸入參數映射:輸入參數可以是List,Map集合類型,也可以是基本類型或POJO類型,輸入參數映射類似於JDBC的preparedStatement設置參數的過程。
- 輸出參數映射:輸出參數可以實List,Map集合類型,也可以是基本類型或POJO類型,輸出參數類似於JDBC對結果集的解析過程。
2.3 MyBatis的功能架構是怎樣的
Mybatis的功能架構分爲三層:
- API接口層:提供外部使用的接口API,開發人員通過這些API來操作數據庫,接口層一般接收調用請求後,就要來調用數據處理層來完成具體的數據處理。
- 數據處理層:負責具體的SQL查找,SQL解析,SQL執行和執行結果的映射處理等。它的主要目的是根據調用的請求完成一次數據庫的操作。
- 框架基礎支撐層:負責最基礎的功能支撐,包括連接管理,事務管理,配置加載和緩存處理,這些都是公用的東西,我們把它抽取出來作爲最基礎的組件,爲上層數據處理層提供最基礎的支撐。
2.4 MyBatis的框架架構設計是怎麼樣的
從上圖可以發現,MyBatis的初始化從加載主配置文件開始,生成Configuration類
- 加載配置:可以從代碼的註解或xml中加載,將SQL的配置信息加載成一個個MappedStatement對象,(包括了傳參映射配置,執行SQL,結果集映射配置)存儲到內存中。
- SQL解析:當API接口層接收到調用請求後,會接收到傳入的SQL的id和傳入對象,(可以是Map或JavaBean對象),MyBatis會根據SQL的id找到相應的MapperStatement,然後根據傳入的參數對MapperStatement進行解析,解析後得到最終要執行的SQL語句和參數。
- SQL執行:將最終得到的SQL和參數拿到數據庫進行執行,得到操作數據庫的結果。
- 結果映射:將數據庫的查詢結果根據映射配置進行轉換,可以轉換成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 #{}和${}的區別
- ${}傳入的值直接出現在SQL中,所以一般使用外部要加上 ‘’,而#{}會自動加上‘’
- #{}可以防止SQL注入問題,而${}不能,使用#{}傳參更加安全
- #{}的變量替換是在DBMS內,${}的變量替換是在DBMS外部
- #{}傳入的值會經過預編譯處理生成 ? 佔位符,調用preparedStatement的set方法爲其賦值
- 一般能使用#{}的地方儘量不要使用${}
參考: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中如何傳遞多個參數
- 順序傳參法
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語句表達不直觀,一旦出錯很難發現錯誤。
- @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屬性了,該屬性必須是指定的,但是在不用情況下,該屬性的值是不一樣的。主要有三種情況:
- 如果傳入的單參數類型是一個List,collection的屬性值爲list
- 如果傳入的單參數類型是一個array數組,collection的屬性值爲array
- 不管傳入單參還是多參,都可以將其封裝成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接口調用時有哪些要求?
- Mapper接口方法名必須和mapperx.xml每個sql的id一致
- Mapper接口方法的參數類型必須和mapper.xml中每個sql的parameterType類型一致,可以取別名,只有一個參數時parameterType屬性可以不寫
- Mapper接口方法的返回參數類型必須和mapper.xml中每個sql的resultType類型一致
- mapper.xml的namespace必須是Mapper接口的全限定類名
3.10 Mapper 編寫有哪幾種方式?
總共有三種方案。
- 使用mapper掃描器
-
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>
-
定義mapper接口
注意mapper.xml的文件名必須和mapper接口名必須保持一致並且放在同一目錄下。 -
配置mapper掃描
<bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="mapper接口權限地類名"/> <property name="SqlSessionFactoryBeanName" value="sessionFactory"/> </bean>
-
使用掃描器後可以從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>