蓮花池--MyBatis系列之面試常見問題

簡單的小宋發表於2020-11-27

本章節小宋會帶大家去揭幕MyBaitis的一些問題,從原理到原始碼角度分析,希望各位同學能從系列部落格中,學到一些好的技術點。那我們直接進入主題。

文章目錄

MyBatis常見問題總結

#{}和${}的區別是什麼?

${}是 Properties 檔案中的變數佔位符,它可以用於標籤屬性值和 sql 內部,屬於靜態文字替換,
比如${driver}會被靜態替換為com.mysql.jdbc.Driver。
#{}是 sql 的引數佔位符,MyBatis 會將 sql 中的#{}替換為?號,
在 sql 執行前會使用 PreparedStatement 的引數設定方法,按序給 sql 的?號佔位符設定引數值,
比如 ps.setInt(0, parameterValue),
#{item.name} 的取值方式為使用反射從引數物件中獲取 item 物件的 name 屬性值,
相當於 param.getItem().getName()。

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>為不支援自增的主鍵生成策略標籤。

最佳實踐中,通常一個 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 中,每一個、、、標籤,都會被解析為一個MappedStatement物件。

Dao 介面裡的方法,是不能過載的,因為是全限名+方法名的儲存和尋找策略。

Dao 介面的工作原理是 JDK 動態代理,MyBatis 執行時會使用 JDK 動態代理為 Dao 介面生成代理 proxy 物件,代理物件 proxy 會攔截介面方法,轉而執行MappedStatement所代表的 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 執行批量插入,能返回資料庫主鍵列表嗎?

能,JDBC 能,MyBatis 當然也能。

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 是如何將 sql 執行結果封裝為目標物件並返回的?都有哪些對映形式?

第一種是使用標籤,逐一定義列名和物件屬性名之間的對映關係。第二種是使用 sql 列的別名功能,將列別名書寫為物件屬性名,比如 T_NAME AS NAME,物件屬性名一般是 name,小寫,但是列名不區分大小寫,MyBatis 會忽略列名大小寫,智慧找到與之對應物件屬性名,你甚至可以寫成 T_NAME AS NaMe,MyBatis 一樣可以正常工作。

有了列名與屬性名的對映關係後,MyBatis 通過反射建立物件,同時使用反射給物件的屬性逐一賦值並返回,那些找不到對映關係的屬性,是無法完成賦值的。

MyBatis 能執行一對一、一對多的關聯查詢嗎?都有哪些實現方式,以及它們之間的區別?

能,MyBatis 不僅可以執行一對一、一對多的關聯查詢,還可以執行多對一,多對多的關聯查詢,多對一查詢,其實就是一對一查詢,只需要把 selectOne()修改為 selectList()即可;多對多查詢,其實就是一對多查詢,只需要把 selectOne()修改為 selectList()即可。

關聯物件查詢,有兩種實現方式,一種是單獨傳送一個 sql 去查詢關聯物件,賦給主物件,然後返回主物件。另一種是使用巢狀查詢,巢狀查詢的含義為使用 join 查詢,一部分列是 A 物件的屬性值,另外一部分列是關聯物件 B 的屬性值,好處是隻發一個 sql 查詢,就可以把主物件和其關聯物件查出來。

那麼問題來了,join 查詢出來 100 條記錄,如何確定主物件是 5 個,而不是 100 個?其去重複的原理是標籤內的子標籤,指定了唯一確定一條記錄的 id 列,MyBatis 根據列值來完成 100 條記錄的去重複功能,可以有多個,代表了聯合主鍵的語意。

同樣主物件的關聯物件,也是根據這個原理去重複的,儘管一般情況下,只有主物件會有重複記錄,關聯物件一般不會重複。

舉例:下面 join 查詢出來 6 條記錄,一、二列是 Teacher 物件列,第三列為 Student 物件列,MyBatis 去重複處理後,結果為 1 個老師 6 個學生,而不是 6 個老師 6 個學生。

t_id t_name s_id

| 1 | teacher | 38 | | 1 | teacher | 39 | | 1 | teacher | 40 | | 1 | teacher | 41 | | 1 | teacher | 42 | | 1 | teacher | 43 |

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 的 Xml 對映檔案中,不同的 Xml 對映檔案,id 是否可以重複?

不同的 Xml 對映檔案,如果配置了 namespace,那麼 id 可以重複;如果沒有配置 namespace,那麼 id 不能重複;畢竟 namespace 不是必須的,只是最佳實踐而已。

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

MyBatis 中如何執行批處理?

使用 BatchExecutor 完成批處理。

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 配置檔案中,可以指定預設的 ExecutorType 執行器型別,也可以手動給 DefaultSqlSessionFactory 的建立 SqlSession 的方法傳遞 ExecutorType 型別引數。

MyBatis 是否可以對映 Enum 列舉類?

MyBatis 可以對映列舉類,不單可以對映列舉類,MyBatis 可以對映任何物件到表的一列上。對映方式為自定義一個 TypeHandler,實現 TypeHandler 的 setParameter()和 getResult()介面方法。TypeHandler 有兩個作用,一是完成從 javaType 至 jdbcType 的轉換,二是完成 jdbcType 至 javaType 的轉換,體現為 setParameter()和 getResult()兩個方法,分別代表設定 sql 問號佔位符引數和獲取列查詢結果。

MyBatis 對映檔案中,如果 A 標籤通過 include 引用了 B 標籤的內容,請問,B 標籤能否定義在 A 標籤的後面,還是說必須定義在 A 標籤的前面?

雖然 MyBatis 解析 Xml 對映檔案是按照順序解析的,但是,被引用的 B 標籤依然可以定義在任何地方,MyBatis 都可以正確識別。

原理是,MyBatis 解析 A 標籤,發現 A 標籤引用了 B 標籤,但是 B 標籤尚未解析到,尚不存在,此時,MyBatis 會將 A 標籤標記為未解析狀態,然後繼續解析餘下的標籤,包含 B 標籤,待所有標籤解析完畢,MyBatis 會重新解析那些被標記為未解析的標籤,此時再解析 A 標籤時,B 標籤已經存在,A 標籤也就可以正常解析完成了。

簡述 MyBatis 的 Xml 對映檔案和 MyBatis 內部資料結構之間的對映關係?

MyBatis 將所有 Xml 配置資訊都封裝到 All-In-One 重量級物件 Configuration 內部。在 Xml 對映檔案中,標籤會被解析為 ParameterMap 物件,其每個子元素會被解析為 ParameterMapping 物件。標籤會被解析為 ResultMap 物件,其每個子元素會被解析為 ResultMapping 物件。每一個、、、標籤均會被解析為 MappedStatement 物件,標籤內的 sql 會被解析為 BoundSql 物件。

為什麼說 MyBatis 是半自動 ORM 對映工具?它與全自動的區別在哪裡?

Hibernate 屬於全自動 ORM 對映工具,使用 Hibernate 查詢關聯物件或者關聯集合物件時,可以根據物件關係模型直接獲取,所以它是全自動的。而 MyBatis 在查詢關聯物件或關聯集合物件時,需要手動編寫 sql 來完成,所以,稱之為半自動 ORM 對映工具。

面試題看似都很簡單,但是想要能正確回答上來,必定是研究過原始碼且深入的人,而不是僅會使用的人或者用的很熟的人,以上所有面試題及其答案所涉及的內容,在我的 MyBatis 系列部落格中都有詳細講解和原理分析。

相關文章