面試必會 --> MyBatis篇
什么是MyBatis
-
Mybatis是一個半ORM(對象關系映射)框架,它內部封裝了JDBC,開發時只需要關注SQL語句本身,不需要花費精力去處理加載驅動、創建連接、創建statement等繁雜的過程。程序員直接編寫原生態sql,可以嚴格控制sql執行性能,靈活度高。
-
MyBatis 可以使用 XML 或注解來配置和映射原生信息,將 POJO映射成數據庫中的記錄,避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。
-
通過xml 文件或注解的方式將要執行的各種 statement 配置起來,并通過java對象和statement中sql的動態參數進行映射生成最終執行的sql語句,最后由mybatis框架執行sql并將結果映射為java對象并返回。(從執行sql到返回result的過程)。
說說MyBatis的優點和缺點
優點:
- 基于SQL語句編程,相當靈活,不會對應用程序或者數據庫的現有設計造成任何影響,SQL寫在XML里,解除sql與程序代碼的耦合,便于統一管理;提供XML標簽,支持編寫動態SQL語句,并可重用。
- 與JDBC相比,減少了50%以上的代碼量,消除了JDBC大量冗余的代碼,不需要手動開關連接;
- 很好的與各種數據庫兼容(因為MyBatis使用JDBC來連接數據庫,所以只要JDBC支持的數據庫MyBatis都支持)。
- 能夠與Spring很好的集成;
- 提供映射標簽,支持對象與數據庫的ORM字段關系映射;提供對象關系映射標簽,支持對象關系組件維護。
缺點:
- SQL語句的編寫工作量較大,尤其當字段多、關聯表多時,對開發人員編寫SQL語句的功底有一定要求。
- SQL語句依賴于數據庫,導致數據庫移植性差,不能隨意更換數據庫。
#{}和${}的區別是什么?
- #{}是預編譯處理,${}是字符串替換。
- Mybatis在處理#{}時,會將sql中的#{}替換為?號,調用PreparedStatement的set方法來賦值;
- Mybatis在處理${}時,就是把\${}替換成變量的值。
- 使用#{}可以有效的防止SQL注入,提高系統安全性。
當實體類中的屬性名和表中的字段名不一樣 ,怎么辦 ?
-
第1種: 通過在查詢的sql語句中定義字段名的別名,讓字段名的別名和實體類的屬性名一致.
<select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”> select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; </select>
-
第2種: 通過來映射字段名和實體類屬性名的一一對應的關系。
<select id="getOrder" parameterType="int" resultMap="orderresultmap"> select * from orders where order_id=#{id} </select> <resultMap type=”me.gacl.domain.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>
Mybatis是如何進行分頁的?分頁插件的原理是什么?
Mybatis使用RowBounds對象進行分頁,它是針對ResultSet結果集執行的內存分頁,而非物理分頁。可以在sql內直接拼寫帶有物理分頁的參數來完成物理分頁功能,也可以使用分頁插件來完成物理分頁,比如:MySQL數據的時候,在原有SQL后面拼寫limit。
分頁插件的基本原理是使用Mybatis提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的sql,然后重寫sql,根據dialect方言,添加對應的物理分頁語句和物理分頁參數。
Mybatis是如何將sql執行結果封裝為目標對象并返回的?都有哪些映射形式?
- 第一種是使用標簽,逐一定義數據庫列名和對象屬性名之間的映射關系。
- 第二種是使用sql列的別名功能,將列的別名書寫為對象屬性名。
有了列名與屬性名的映射關系后,Mybatis通過反射創建對象,同時使用反射給對象的屬性逐一賦值并返回,那些找不到映射關系的屬性,是無法完成賦值的。
如何執行批量插入?
首先,創建一個簡單的insert語句:
<insert id=”insertname”>
insert into names (name) values (#{value})
</insert>
然后在java代碼中像下面這樣執行批處理插入:
list<string> names = new arraylist();
names.add(“fred”);
names.add(“barney”);
names.add(“betty”);
names.add(“wilma”);
// 注意這里 executortype.batch
sqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);
try {
namemapper mapper = sqlsession.getmapper(namemapper.class);
for (string name : names) {
mapper.insertname(name);
}
sqlsession.commit();
}catch(Exception e){
e.printStackTrace();
sqlSession.rollback();
throw e;
}
finally {
sqlsession.close();
}
Xml映射文件中,除了常見的select|insert|updae|delete標簽之外,還有哪些標簽?
加上動態sql的9個標簽,其中為sql片段標簽,通過標簽引入sql片段,為不支持自增的主鍵生成策略標簽。
MyBatis實現一對一有幾種方式?具體怎么操作的?
有聯合查詢和嵌套查詢,聯合查詢是幾個表聯合查詢,只查詢一次, 通過在resultMap里面配置association節點配置一對一的類就可以完成;
嵌套查詢是先查一個表,根據這個表里面的結果的 外鍵id,去再另外一個表里面查詢數據,也是通過association配置,但另外一個表的查詢通過select屬性配置。
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的緩存機制:
Mybatis整體:

一級緩存localCache
在應用運行過程中,我們有可能在一次數據庫會話中,執行多次查詢條件完全相同的 SQL,MyBatis 提供了一級緩存的方案優化這部分場景,如果是相同的 SQL 語句,會優先命中一級緩存,避免直接對數據庫進行查詢,提高性能。
每個 SqlSession 中持有了 Executor,每個 Executor 中有一個 LocalCache。當用戶發起查詢時,MyBatis 根據當前執行的語句生成 MappedStatement,在 Local Cache 進行查詢,如果緩存命中的話,直接返回結果給用戶,如果緩存沒有命中的話,查詢數據庫,結果寫入 Local Cache,最后返回結果給用戶。具體實現類的類關系圖如下圖所示:

- MyBatis 一級緩存的生命周期和 SqlSession 一致。
- MyBatis 一級緩存內部設計簡單,只是一個沒有容量限定的 HashMap,在緩存的功能性上有所欠缺。
- MyBatis 的一級緩存最大范圍是 SqlSession 內部,有多個 SqlSession 或者分布式的環境下,數據庫寫操作會引起臟數據,建議設定緩存級別為 Statement。
二級緩存
在上文中提到的一級緩存中,其最大的共享范圍就是一個 SqlSession 內部,如果多個 SqlSession之間需要共享緩存,則需要使用到二級緩存。開啟二級緩存后,會使用 CachingExecutor 裝飾Executor,進入一級緩存的查詢流程前,先在 CachingExecutor 進行二級緩存的查詢,具體的工作流程如下所示。

二級緩存開啟后,同一個 namespace 下的所有操作語句,都影響著同一個 Cache,即二級緩存被多個 SqlSession 共享,是一個全局的變量。
當開啟緩存后,數據的查詢執行的流程為:
二級緩存 -> 一級緩存 -> 數據庫
- MyBatis 的二級緩存相對于一級緩存來說,實現了 SqlSession 之間緩存數據的共享,同時粒度更加細,能夠到 namespace 級別,通過 Cache 接口實現類不同的組合,對 Cache 的可控性也更強。
- MyBatis 在多表查詢時,極大可能會出現臟數據,有設計上的缺陷,安全使用二級緩存的條件比較苛刻。
- 在分布式環境下,由于默認的 MyBatis Cache 實現都是基于本地的,分布式環境下必然會出現讀取到臟數據,需要使用集中式緩存將 MyBatis 的 Cache 接口實現,有一定的開發成本,直接使用 Redis、Memcached 等分布式緩存可能成本更低,安全性也更高。
JDBC 編程有哪些步驟?
-
裝載相應的數據庫的 JDBC 驅動并進行初始化:
Class.forName("com.mysql.jdbc.Driver");
-
建立 JDBC 和數據庫之間的 Connection 連接:
Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test? characterEncoding=UTF-8", "root", "123456");
- 創建 Statement 或者 PreparedStatement 接口,執行 SQL 語句。
- 處理和顯示結果。
- 釋放資源。
MyBatis 中見過什么設計模式?

MyBatis 中比如 UserMapper.java 是接口,為什么沒有實現類還能調用?
使用JDK動態代理+MapperProxy。本質上調用的是MapperProxy的invoke方法。

浙公網安備 33010602011771號