博客 / 詳情

返回

MyBatis 常見面試題

Mybatis基礎

Mybatis是什麼?

  • MyBatis框架是一個開源的數據持久層框架。
  • 它的內部封裝了通過JDBC訪問數據庫的操作,支持普通的SQL查詢、存儲過程和高級映射,幾乎消除了所有的JDBC代碼和參數的手工設置以及結果集的檢索。
  • MyBatis作為持久層框架,其主要思想是將程序中的大量SQL語句剝離出來,配置在配置文件當中,實現SQL的靈活配置。
  • 這樣做的好處是將SQL與程序代碼分離,可以在不修改代碼的情況下,直接在配置文件當中修改SQL。

為什麼使用Mybatis代替JDBC?

MyBatis 是一種優秀的 ORM(Object-Relational Mapping)框架,與 JDBC 相比,有以下幾點優勢:

  1. 簡化了 JDBC 的繁瑣操作:使用 JDBC 進行數據庫操作需要編寫大量的樣板代碼,如獲取連接、創建 Statement/PreparedStatement,設置參數,處理結果集等。而使用 MyBatis 可以將這些操作封裝起來,通過簡單的配置文件和 SQL 語句就能完成數據庫操作,從而大大簡化了開發過程。
  2. 提高了 SQL 的可維護性:使用 JDBC 進行數據庫操作,SQL 語句通常會散佈在代碼中的各個位置,當 SQL 語句需要修改時,需要找到所有使用該語句的地方進行修改,這非常不方便,也容易出錯。而使用 MyBatis,SQL 語句都可以集中在配置文件中,可以更加方便地修改和維護,同時也提高了 SQL 語句的可讀性。
  3. 支持動態 SQL:MyBatis 提供了強大的動態 SQL 功能,可以根據不同的條件生成不同的 SQL 語句,這對於複雜的查詢操作非常有用。
  4. 易於集成:MyBatis 可以與 Spring 等流行的框架集成使用,可以通過 XML 或註解配置進行靈活的配置,同時 MyBatis 也提供了非常全面的文檔和示例代碼,學習和使用 MyBatis 非常方便。

綜上所述,使用 MyBatis 可以大大簡化數據庫操作的代碼,提高 SQL 語句的可維護性和可讀性,同時還提供了強大的動態 SQL 功能,易於集成使用。因此,相比於直接使用 JDBC,使用 MyBatis 更為便捷、高效和方便。

然而,也要注意一些缺點:

  • 雖然 MyBatis 很強大,但編寫 SQL 語句可能會相對繁瑣,特別是當涉及多個字段或多個關聯表時。這就要求開發人員在 SQL 編寫方面有一定的功底。
  • 由於 SQL 語句依賴於特定的數據庫,如果想要更換數據庫,移植性就會受到影響。這意味着不能輕易地更改數據庫,可能需要進行一些適應性的修改。

ORM是什麼

ORM(Object Relational Mapping),對象關係映射,是一種為了解決關係型數據庫數據與簡單Java對象(POJO)的映射關係的技術。簡單的説,ORM是通過使用描述對象和數據庫之間映射的元數據,將程序中的對象自動持久化到關係型數據庫中。

Mybatis和Hibernate的區別?

主要有以下幾點區別:

  1. Hibernate的開發難度大於MyBatis,主要由於Hibernate比較複雜,龐大,學習週期比較長。
  2. Hibernate屬於全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,可以根據對象關係模型直接獲取,所以它是全自動的。而Mybatis在查詢關聯對象或關聯集合對象時,需要手動編寫sql來完成,所以,稱之為半自動ORM映射工具。
  3. 數據庫擴展性的區別。Hibernate與數據庫具體的關聯在XML中,所以HQL對具體是用什麼數據庫並不是很關心。MyBatis由於所有sql都是依賴數據庫書寫的,所以擴展性、遷移性比較差。
  4. 緩存機制的區別。Hibernate的二級緩存配置在SessionFactory生成配置文件中進行詳細配置,然後再在具體的表對象映射中配置那種緩存。MyBatis的二級緩存配置都是在每個具體的表對象映射中進行詳細配置,這樣針對不同的表可以自定義不同的緩衝機制,並且MyBatis可以在命名空間中共享相同的緩存配置和實例,通過Cache-ref來實現。
  5. 日誌系統完善性的區別。Hibernate日誌系統非常健全,涉及廣泛,而Mybatis則除了基本記錄功能外,功能薄弱很多。
  6. sql的優化上,Mybatis要比Hibernate方便很多。由於Mybatis的sql都是寫在xml裏,因此優化sql比Hibernate方便很多。而Hibernate的sql很多都是自動生成的,無法直接維護sql;總之寫sql的靈活度上Hibernate不及Mybatis。

MyBatis 與 JPA 有哪些不同?

JPA是Java Persistence API的簡稱,中文名Java持久層API,是JDK 5.0註解或XML描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。

首先,我們來聊聊編程模型。MyBatis和JPA採用了不同的方式來處理數據操作。

  • MyBatis使用基於SQL的編程模型,這意味着開發人員需要自己編寫SQL語句,並將它們映射到Java方法。這給開發人員提供了更大的靈活性,可以精確地控制SQL的編寫和執行過程。
  • JPA則採用了基於對象的編程模型,你只需定義實體類並使用註解或XML配置來將實體映射到數據庫表。JPA會自動生成SQL語句,開發人員不必過多關心底層SQL的細節。

其次,我們來看一下SQL控制。

  • 在MyBatis中,可以編寫和優化SQL語句,這在需要特定優化或使用數據庫特性時非常有用。
  • JPA則將大部分SQL細節隱藏起來,自動生成SQL語句。這使得開發人員無需深入瞭解底層SQL,但在某些情況下可能會影響性能或限制你的操作。

接下來是靈活性和控制

  • MyBatis提供了更多的靈活性,適用於需要定製化SQL查詢或調用存儲過程的場景。
  • JPA則提供了更高層次的抽象,用於簡化常見數據庫操作。然而,這也可能會在某些高級或複雜情況下產生一些限制。

關於查詢語言

  • MyBatis使用原生SQL作為查詢語言,這要求開發人員對SQL有一定了解。
  • JPA則引入了JPQL作為查詢語言,它更加面向對象,類似於SQL,但操作的是實體對象。

在緩存方面

  • MyBatis的緩存控制更精細,可以更準確地控制緩存行為。
  • JPA也支持緩存,但通常對緩存的控制較少,更多地由框架自動管理。

總的來説,選擇使用MyBatis還是JPA取決於項目需求和團隊技術背景。如果你需要更多的SQL控制和定製化,MyBatis可能更適合;如果你希望更快速地進行常見數據庫操作,JPA可能更適合

為什麼説 Mybatis 是半ORM 映射工具?

首先,Mybatis被稱為半ORM框架是因為它在數據庫操作方面提供了一些對象關係映射的功能,但相對於全ORM框架,它更加靈活和輕量級。在Mybatis中,我們需要手動編寫SQL來執行數據庫操作,這跟傳統的JDBC方式有點類似。但是,Mybatis通過映射文件來實現Java對象與數據庫表之間的映射,這就是它的ORM特性。

區別的話,全ORM框架通常更加自動化,它會完全代替你來生成SQL語句,進行數據庫操作。這在某些情況下能夠提高開發效率,因為你不需要寫太多的SQL代碼。但是,全ORM框架也可能在性能方面略有影響,因為它們可能會生成複雜的SQL語句,導致查詢效率下降。

相比之下,Mybatis更加靈活,你可以精確地控制要執行的SQL語句,這對於需要優化查詢性能的場景很有幫助。另外,Mybatis在映射文件中可以明確指定每個字段的映射關係,這樣你能更好地控制數據庫表和Java對象之間的對應關係。

MyBatis框架的優缺點及其適用的場合

優點

  1. 與JDBC相比,減少了50%以上的代碼量。
  2. MyBatis是易學的持久層框架,小巧並且簡單易學。
  3. MyBatis相當靈活,不會對應用程序或者數據庫的現有設計強加任何影響,SQL寫在XML文件裏,從程序代碼中徹底分離,降低耦合度,便於統一的管理和優化,並可重用。
  4. 提供XML標籤,支持編寫動態的SQL,滿足不同的業務需求。
  5. 提供映射標籤,支持對象與數據庫的ORM字段關係映射。

缺點

  1. SQL語句的編寫工作量較大,對開發人員編寫SQL的能力有一定的要求。
  2. SQL語句依賴於數據庫,導致數據庫不具有好的移植性,不可以隨便更換數據庫。

適用場景

MyBatis專注於SQL自身,是一個足夠靈活的DAO層解決方案。對性能的要求很高,或者需求變化較多的項目,例如Web項目,那麼MyBatis是不二的選擇。

Mybatis原理

MyBatis的核心組件有哪些?

  • SqlSessionFactoryBuilder:是創建 SqlSessionFactory 的構建器。它使用配置文件或配置類來創建 SqlSessionFactory。SqlSessionFactoryBuilder 本身是一個工具類,通常在應用程序啓動時使用一次,之後就可以丟棄。
  • SqlSessionFactory,是一個會話工廠,同時也承擔了配置數據庫連接信息和事務管理的功能。它的任務是創建 SqlSession 對象,這個對象是我們與數據庫交互的主要途徑。一旦這個工廠被建立起來,它就會加載一些必要的配置和映射文件,為後續的數據庫操作提供一個可靠的基礎。
  • SqlSession,是業務與數據庫交互的窗口,能夠執行 SQL 語句,提交或回滾事務,還可以獲取 Mapper 接口的實例。不過需要注意的是,SqlSession 的生命週期是短暫的,通常在數據庫操作完成後就應該關閉它,這樣可以釋放資源。
  • Mapper 接口,MyBatis 通過動態代理的方式,把接口方法和映射文件中的 SQL 語句關聯起來,這樣我們就可以方便地通過接口來執行數據庫操作。
  • Mapper 映射文件,可以定義 SQL 語句、參數映射、結果映射等等。裏面的 SQL 語句可以包括增刪改查等操作,MyBatis 會根據我們調用的方法來選擇正確的 SQL 語句來執行。
  • Type Handlers:負責將預處理語句中的參數從 Java 類型轉換為 JDBC 類型,以及將結果集中的列從 JDBC 類型轉換為 Java 類型。MyBatis 內置了許多默認的 Type Handlers,並且允許用户自定義 Type Handler。
  • Result Maps:描述了數據庫結果集與對象屬性之間的映射關係。這是 MyBatis 中最強大的特性之一,允許你處理複雜的映射場景,如嵌套結果和關聯查詢。
  • Caching: MyBatis 支持一級緩存和二級緩存。一級緩存默認開啓,作用範圍是同一個 SqlSession;二級緩存需要手動配置,可以在不同的 SqlSession 之間共享緩存數據,提高系統性能。
  • Transaction Manager:MyBatis 提供了一個簡單的事務管理機制,可以與 Spring 框架的事務管理集成。這使得開發者能夠以聲明式的方式管理事務,而不是編程式地處理事務邏輯。

Mybatis的工作原理

  • 讀取MyBatis配置文件:mybatis-config.xml為MyBatis的全局配置文件,配置了MyBatis的運行環境等信息,例如數據庫連接信息。
  • 加載映射文件。映射文件即SQL映射文件,該文件中配置了操作數據庫的SQL語句,需要在MyBatis配置文件mybatis-config.xml中加載。mybatis-config.xml文件可以加載多個映射文件,每個文件對應數據庫中的一張表。
  • 構造會話工廠:通過MyBatis的環境等配置信息構建會話工廠SqlSessionFactory。
  • 創建會話對象:由會話工廠創建SqlSession對象,該對象中包含了執行SQL語句的所有方法。
  • 執行SQL語句:MyBatis底層定義了一個Executor 接口來操作數據庫,它將根據SqlSession傳遞的參數動態地生成需要執行的SQL語句,同時負責查詢緩存的維護。
  • MappedStatement 對象:在Executor接口的執行方法中有一個MappedStatement類型的參數,該參數是對映射信息的封裝,用於存儲要映射的SQL語句的id、參數等信息。
  • 輸入參數映射:輸入參數類型可以是Map、List等集合類型,也可以是基本數據類型和POJO類型。輸入參數映射過程類似於 JDBC對preparedStatement對象設置參數的過程。
  • 輸出結果映射:輸出結果類型可以是Map、List等集合類型,也可以是基本數據類型和POJO類型。輸出結果映射過程類似於 JDBC對結果集的解析過程。

能詳細説説 MyBatis 的執行流程嗎?

MyBatis 的執行原理基於其核心設計思想:通過映射文件(XML 或註解)將 SQL 語句與 Java 對象進行綁定,整個執行流程可以分為以下幾個步驟:

  1. SqlSessionFactory 的創建:MyBatis 的執行過程從 SqlSessionFactory 開始。SqlSessionFactory 是一個工廠類,負責創建 SqlSession實例 ,SqlSession 是 MyBatis 與數據庫交互的核心對象。SqlSessionFactory 是通過 SqlSessionFactoryBuilder 構建的(通常是通過讀取 MyBatis 配置文件 mybatis-config.xml 來初始化)。
  2. Sqlsession 的獲取:SqlSessionFactory 通過 openSsesion()方法獲取一個 SqlSession 對象,Sqlsession 是操作數據庫的主要入口,用户通過它執行SQL語句、提交事務等。
  3. 執行映射語句:當調用sqlsession的方法(例如selectone、selectlist、insert、update、delete 等)時,MyBatis 會根據傳入的 SQL映射語句的 ID,去尋找對應的 SQL語句執行。
  4. 命名空間和映射語句的查詢:SQL映射語句通過映射文件中的 namespace 和 id 進行定位。MyBatis 會將映射文件解析成一個 MappedStateanent 對象,MappedStateanent 保存了SQL語句、參數類型、返回類型等信息。
  5. 參數封裝和 SQL語句執行:在SQL執行前,MyBatis會根據映射文件中配置的 parameterType 類型,將傳入的參數封裝成適當的時象(例如,使用 JavaBean、Map、XML格式等。然後,MyBatis 會很據不同的執行環境(如 MySQL、Oracle 等數據庫),將 SQL語句執行到數據庫中,並將查詢結果通過映射文件中配置的 resultType 類型返回。
  6. 返回結果的映射:MyBatis會將查詢結果根據resultType或 resulMap進行轉換,將查詢結果轉換成Java 對象(如List、Map 或指定的 POJO 類)。
  7. 事務管理:Matis的事務管理通過 Sqlsession 來處理,Sqlsession 提供了事務的提交和回滾方法,當調用 Sqlsession.commit()時,SQL執行的結果會被提交到數據庫;若發生異常,Sqlsession.roolback(),事務會被回滾。
  8. 最後關閉 SqlSession:在操作完成後,Sqlsession 會通過 close()方法關閉,釋放數據庫連接和資源。

簡述 MyBatis 的插件運行原理,Mybatis的插件能夠在哪些地方進行攔截?

MyBatis 的插件機制是通過 動態代理 實現的,主要是在 SQL 執行的關鍵點(如執行查詢、更新、插入)攔截操作並增強功能。

MyBatis的插件可以在MyBatis的執行過程中的多個關鍵點進行攔截和干預。這些關鍵點包括:

  1. Executor(執行器)層面的攔截: 這是SQL語句的執行層面,插件可以在SQL語句執行前後進行攔截。這包括了SQL的預處理、參數設置、查詢結果的映射等。
  2. StatementHandler(語句處理器)層面的攔截: 這是對SQL語句的處理層面,插件可以在SQL語句被執行之前進行攔截,你可以在這裏修改、替換、生成SQL語句。
  3. ParameterHandler(參數處理器)層面的攔截: 這是處理參數的層面,插件可以在參數傳遞給SQL語句之前進行攔截,你可以在這裏修改參數值。
  4. ResultSetHandler(結果集處理器)層面的攔截: 這是處理查詢結果的層面,插件可以在查詢結果返回給調用方之前進行攔截,你可以在這裏對查詢結果進行修改、處理。

Mybatis使用JDK的動態代理,為需要攔截的接口生成代理對象以實現接口方法攔截功能,每當執行這4種接口對象的方法時,就會進入攔截方法,具體就是InvocationHandler的invoke()方法,當然,只會攔截那些你指定需要攔截的方法。

如何編寫一個插件?

插件機制的核心是Interceptor接口,你可以實現這個接口,編寫自己的插件邏輯。一個插件主要包括以下幾個步驟:

  1. 實現Interceptor接口: 創建一個類,實現MyBatis提供的Interceptor接口,該接口包含了intercept和plugin兩個方法。
  2. 實現intercept方法: intercept方法是插件的核心,它會在方法執行前後進行攔截。你可以在這個方法中編寫自己的邏輯。
  3. 實現plugin方法: plugin方法用於創建代理對象,將插件包裝在目標對象上,使得插件邏輯能夠被執行。
  4. 配置插件: 在MyBatis的配置文件中,通過<plugins>標籤配置你的插件。通常需要指定插件類和一些參數。

Mybatis 是如何進行分頁的?

Mybatis 使用 RowBounds 對象進行分頁,它是針對 ResultSet 結果集執行的內存分頁,而非物理分頁,先把數據都查出來,然後再做分頁。

可以在 sql 內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。

常用的分頁插件和技巧:

  1. PageHelper 插件: PageHelper 是一個流行的 MyBatis 分頁插件,它簡化了分頁查詢的操作。只需要在查詢方法前調用 PageHelper.startPage(pageNum, pageSize),然後執行查詢語句,PageHelper 就會自動處理分頁邏輯。
  2. 使用 RowBounds: 在 MyBatis 中,你還可以使用 RowBounds 對象來實現分頁查詢。通過在查詢方法中傳遞一個 RowBounds 對象,你可以指定從哪一行開始取數據,以及每頁顯示多少條數據。
  3. 自定義分頁插件: 如果你有特殊的分頁需求,你可以編寫自己的分頁插件。這可能涉及到在 MyBatis 的攔截器鏈中插入你自己的邏輯,以實現定製化的分頁處理。

分頁插件的基本原理是什麼?

分頁插件是一種擴展機制,它允許MyBatis在查詢過程中,自動應用分頁邏輯而不需要手動編寫分頁查詢語句。分頁插件的一般原理如下:

  1. 攔截器(Interceptor):分頁插件實際上是MyBatis的一個攔截器,它可以在查詢被執行之前或之後進行干預。
  2. 處理分頁邏輯:在查詢執行之前,分頁插件會檢測是否有分頁參數傳入。如果有分頁參數,插件會根據數據庫方言生成適當的分頁查詢語句。
  3. 修改查詢參數:插件會修改查詢的SQL語句,添加分頁的限制條件。同時,它還會修改參數對象,將分頁參數替換為實際的分頁偏移量(offset)和每頁條數(limit)。
  4. 執行查詢:修改後的查詢語句被執行,得到查詢結果。
  5. 封裝分頁結果:插件會根據查詢結果和分頁參數,將查詢結果進行切割,得到分頁後的結果。

分頁插件的基本原理是使用 Mybatis 提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的 sql,然後重寫 sql(SQL 拼接 limit),根據 dialect 方言,添加對應的物理分頁語句和物理分頁參數,用到了 JDK 動態代理,用到了責任鏈設計模式。

Mybatis 是否支持延遲加載?

所謂的延遲加載,其實就是一種優化方法,目標是為了在查數據庫的時候,儘量不讀取多餘的數據,從而提高我們應用的表現和節約資源。在MyBatis裏,這個延遲加載的技巧主要是用在處理對象關係映射的時候,也就是ORM。

來個例子幫你理解:假設有兩張表,一張是訂單表,另一張是商品表。每個訂單下面可能有好幾個商品。用延遲加載的話,當我們查一個訂單的時候,MyBatis不會馬上查出這個訂單的所有商品,而是等到我們真的要用商品的數據時才去查。這樣做就避免了在查訂單的時候額外加載了一堆沒用的商品。但要注意,雖然延遲加載能提升性能,可別用得過了,免得碰上懶加載的N+1問題,就是要查很多次才能拿到關聯數據,結果性能就拖垮了。所以用延遲加載的時候,得根據實際情況合理配置和使用。

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,幾乎所有的ORM框架、包括 Hibernate,支持延遲加載的原理都是一樣的。

MyBatis如何處理懶加載和預加載?

當談到MyBatis中的懶加載和預加載時,我們實際上在討論在獲取數據庫數據時如何處理關聯對象的加載方式。

懶加載是一種延遲加載技術,它在需要訪問關聯對象的時候才會加載相關數據。這意味着,當你從數據庫中獲取一個主對象時,它的關聯對象並不會立即加載到內存中,只有當你實際調用訪問關聯對象的方法時,MyBatis才會去數據庫中加載並填充這些關聯對象的數據。懶加載適用於關聯對象較多或者關聯對象數據較大的情況,這樣可以減少不必要的數據庫查詢,提升性能。

預加載則是一種在獲取主對象時同時加載其關聯對象的技術。這樣一來,當獲取主對象時,它的所有關聯對象也會被一併加載到內存中,避免了多次數據庫查詢。預加載適用於你確定在後續使用中肯定會訪問關聯對象,這樣可以減少每次訪問關聯對象時的延遲。

選擇懶加載還是預加載取決於具體需求和場景。如果希望在儘量少的數據庫查詢次數下獲取數據,懶加載是個不錯的選擇。如果在獲取主對象後會頻繁地訪問其關聯對象,預加載可能更適合,因為它可以減少多次查詢帶來的性能開銷。

兩者都是優化數據庫訪問性能的手段,根據具體的使用場景選擇合適的加載方式非常重要。

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

{ } 被解析成預編譯語句,預編譯之後可以直接執行,不需要重新編譯sql。

//sqlMap 中如下的 sql 語句
select * from user where name = #{name};
//解析成為預編譯語句;編譯好SQL語句再取值
select * from user where name = ?;

${ } 僅僅為一個字符串替換,每次執行sql之前需要進行編譯,存在 sql 注入問題。

select * from user where name = '${name}'
//傳遞的參數為 "seven" 時,解析為如下,然後發送數據庫服務器進行編譯。取值以後再去編譯SQL語句。
select * from user where name = "seven";

where 1=1會不會影響性能?

where 1=1 和 <where> 標籤 兩種方案,該如何選擇?

  • 如果 MySQL Server版本小於 5.7,用了 MyBatis的話,建議使用<where> 標籤。
  • 如果 MySQL版本大於等於 5.7,兩個隨便選;因為在MySQL5.7後,有一個所謂的(常量摺疊優化)可以在編譯期消除重言式表達式。
    • 什麼是重言式表達式,就是任何時候永遠都為true的結果, 就會被優化器識別並優化掉,好奇的話你可以通過show warnings 查看,就會發現1=1沒有了。

當然現在 MySQL Server版本基本都是 5.7以上了,不是的話那趕緊升級吧還是。

Mybatis的預編譯

數據庫接受到sql語句之後,需要詞法和語義解析,優化sql語句,制定執行計劃。這需要花費一些時間。如果一條sql語句需要反覆執行,每次都進行語法檢查和優化,會浪費很多時間。預編譯語句就是將sql語句中的值用佔位符替代,即將sql語句模板化。一次編譯、多次運行,省去了解析優化等過程。

mybatis是通過PreparedStatement和佔位符來實現預編譯的。

mybatis底層使用PreparedStatement,默認情況下,將對所有的 sql 進行預編譯,將#{}替換為?,然後將帶有佔位符?的sql模板發送至mysql服務器,由服務器對此無參數的sql進行編譯後,將編譯結果緩存,然後直接執行帶有真實參數的sql。

預編譯的作用:

  1. 預編譯階段可以優化 sql 的執行。預編譯之後的 sql 多數情況下可以直接執行,數據庫服務器不需要再次編譯,可以提升性能。
  2. 預編譯語句對象可以重複利用。把一個 sql 預編譯後產生的 PreparedStatement 對象緩存下來,下次對於同一個sql,可以直接使用這個緩存的 PreparedState 對象。
  3. 防止SQL注入。使用預編譯,而其後注入的參數將不會再進行SQL編譯。也就是説其後注入進來的參數系統將不會認為它會是一條SQL語句,而默認其是一個參數。

MyBatis 如何實現數據庫類型和 Java 類型的轉換的?

MyBatis 類型轉換主要依賴於 MyBatis 的 類型處理器(TypeHandler)機制。

TypeHandler 的核心作用:

  • 將 Java 類型轉換為 JDBC 類型,用於 SQL參數設置
  • 將 JDBC 類型轉換為 Java 類型,用於查詢結果的映射。

具體操作流程如下:

  1. MyBatis 在加載映射文件時,根據字段類型(如 jdbcType)和Java類型(如 resultType確定使用的 TypeHandler。
  2. 在執行SQL時,ParameterHandler會使用TypeHandler 將Java 參數轉換為JDBC 類型
  3. 在解析結果集時,ResultsetHandler使用TypeHandler 將JDBC類型轉換為 Java對象

説説 MyBatis 的緩存機制?

緩存:合理使用緩存是優化中最常見的方法之一,將從數據庫中查詢出來的數據放入緩存中,下次使用時不必從數據庫查詢,而是直接從緩存中讀取,避免頻繁操作數據庫,減輕數據庫的壓力,同時提高系統性能。

Mybatis裏面設計了二級緩存來提升數據的檢索效率,避免每次數據的訪問都需要去查詢數據庫。

一級緩存是SqlSession級別的緩存:Mybatis對緩存提供支持,默認情況下只開啓一級緩存,一級緩存作用範圍為同一個SqlSession。在SQL和參數相同的情況下,我們使用同一個SqlSession對象調用同一個Mapper方法,往往只會執行一次SQL。因為在使用SqlSession第一次查詢後,Mybatis會將結果放到緩存中,以後再次查詢時,如果沒有聲明需要刷新,並且緩存沒超時的情況下,SqlSession只會取出當前緩存的數據,不會再次發送SQL到數據庫。若使用不同的SqlSession,因為不同的SqlSession是相互隔離的,不會使用一級緩存。

與springboot集成時一級緩存不生效問題:一級緩存是會話級別的,要生效的話,必須要在同一個 SqlSession 中。但是與 springboot 集成的 mybatis,默認每次執行sql語句時,都會創建一個新的 SqlSession!所以一級緩存才沒有生效。
解決:加上 @Transactional 註解。如果當前線程存在事務,並且存在相關會話,就從 ThreadLocal 中取出。如果沒有事務,就重新創建一個 SqlSession 並存儲到 ThreadLocal 當中,供下次查詢使用。

二級緩存是mapper級別的緩存:可以使緩存在各個SqlSession之間共享。當多個用户在查詢數據的時候,只要有任何一個SqlSession拿到了數據就會放入到二級緩存裏面,其他的SqlSession就可以從二級緩存加載數據。

主要區別就在於作用範圍:一級緩存只在一個會話內部有效,而二級緩存可以在不同會話之間共享數據。

二級緩存默認不開啓,需要在mybatis-config.xml開啓二級緩存:

<!-- 通知 MyBatis 框架開啓二級緩存 -->
<settings>
  <setting name="cacheEnabled" value="true"/>
</settings>

並在相應的Mapper.xml文件添加cache標籤,表示對哪個mapper 開啓緩存:

<cache/>

二級緩存要求返回的POJO必須是可序列化的,即要求實現Serializable接口。

當開啓二級緩存後,數據的查詢執行的流程就是 二級緩存 -> 一級緩存 -> 數據庫。

為什麼不推薦使用 MyBatis 二級緩存?

  • 有複雜的數據模型或者數據之間的關聯關係的會有數據不一致的影響

二級緩存是以 namespace(mapper) 為單位的,不同 namespace 下的操作互不影響。且 insert/update/delete 操作會清空所在 namespace 下的全部緩存。

那麼問題就出來了,假設現在有 ItemMapper 以及 XxxMapper,在 XxxMapper 中做了表關聯查詢,且做了二級緩存。此時在 ItemMapper 中將 item 信息給刪了,由於不同 namespace 下的操作互不影響,XxxMapper 的二級緩存不會變,那之後再次通過 XxxMapper 查詢的數據就不對了,非常危險。

例如:

@Mapper  
@Repository  
@CacheNamespace  
publicinterfaceXxxMapper{  
  
    @Select("select i.id itemId,i.name itemName,p.amount,p.unit_price unitPrice " +  
            "from item i JOIN payment p on i.id = p.item_id where i.id = #{id}")  
    List<PaymentVO> getPaymentVO(Long id);  
  
}  
  
@Autowired  
private XxxMapper xxxMapper;  
  
@Test  
voidtest(){  
	 System.out.println("==================== 查詢PaymentVO ====================");  
	 List<PaymentVO> voList = xxxMapper.getPaymentVO(1L);  
	 System.out.println(JSON.toJSONString(voList.get(0)));  
	 System.out.println("====================  更新item表的name ==================== ");  
	 Item item = itemMapper.selectById(1);  
	 item.setName("java併發編程");  
	 itemMapper.updateById(item);  
	 System.out.println("====================  重新查詢PaymentVO ==================== ");  
	 List<PaymentVO> voList2 = xxxMapper.getPaymentVO(1L);  
	 System.out.println(JSON.toJSONString(voList2.get(0)));  
}

由於 itemMapper 與 xxxMapper 不是同一個命名空間,所以 itemMapper 執行的更新操作不會影響到 xxxMapper 的二級緩存;

再次調用 xxxMapper.getPaymentVO,發現取出的值是走緩存的,itemName 還是老的。但實際上 itemName 在上面已經被改了

Dao 接口的工作原理是什麼?Dao 接口裏的方法,參數不同時,方法能重載嗎?

最佳實踐中,通常一個 xml 映射文件,都會寫一個 Dao 接口與之對應。Dao 接口就是人們常説的 Mapper 接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中 MappedStatement 的 id 值,接口方法內的參數,就是傳遞給 sql 的參數。 Mapper 接口是沒有實現類的,當調用接口方法時,接口全限名+方法名拼接字符串作為 key 值,可唯一定位一個 MappedStatement ,舉例:com.mybatis3.mappers.StudentDao.findStudentById ,可以唯一找到 namespace 為 com.mybatis3.mappers. StudentDao 下面 id = findStudentByIdMappedStatement 。在 MyBatis 中,每一個 <select><insert><update><delete> 標籤,都會被解析為一個 MappedStatement 對象。

Dao 接口裏的方法可以重載,但是 Mybatis 的 xml 裏面的 ID 不允許重複。並且需要滿足以下條件:

  1. 僅有一個無參方法和一個有參方法
  2. 多個有參方法時,參數數量必須一致。且使用相同的 @Param ,或者使用 param1 這種

Mybatis 版本 3.3.0:

/**
 * Mapper接口裏面方法重載
 */
public interface StuMapper {

 List<Student> getAllStu();

 List<Student> getAllStu(@Param("id") Integer id);
}

然後在 StuMapper.xml 中利用 Mybatis 的動態 sql 就可以實現。

<select id="getAllStu" resultType="com.pojo.Student">
  select * from student
  <where>
    <if test="id != null">
      id = #{id}
    </if>
  </where>
</select>

能正常運行,並能得到相應的結果,這樣就實現了在 Dao 接口中寫重載方法。

Mybatis 的 Dao 接口可以有多個重載方法,但是多個接口對應的映射必須只有一個,否則啓動會報錯。

Dao 接口的工作原理是 JDK 動態代理,MyBatis 運行時會使用 JDK 動態代理為 Dao 接口生成代理 proxy 對象,代理對象 proxy 會攔截接口方法,轉而執行 MappedStatement 所代表的 sql,然後將 sql 執行結果返回。

MyBatis 寫個 Xml 映射文件,再寫個 DAO 接口就能執行,這個原理是什麼?

核心原理是 JDBC 的能力 和 動態代理,通過解析 XML映射文件和動態生成 DAO 接口實現類來完成 SQL的執行。

以下是詳細的執行原理:

  1. 加載配置和 Mapper 映射文件:MyBatis 啓動時,通過配置文件(mybatis-config.xml)加載數據庫連接信息和 Mapper 映射文件(Mapper.xml )XML文件中的 SQL被解析為內部的 MappedStatement 對象,包含 SQL語句、參數映射規則和返回結果映射規則。
  2. 動態代理實現 DAO 接口:MyBatis 為每個 DAO 接口生成一個動態代理類(Mapperproxy ),攔截接口方法調用。動態代理的核心是根據方法名和參數匹配到對應的 MappedStatement ,然後調用JDBC 來執行 SQL。
  3. 通過 JDBC 執行 SQL:MyBatis的 SqlSession 是對JDBC的封裝,它的核心是使用PreparedStatement 來完成SQL的執行。根據 XML 中定義的 SQL和 DAO 接口方法傳入的參數,生成完整的SQL查詢,並將參數通過佔位符(?)綁定到 PreparedStatement 。最終通過JDBC執行SQL,並獲取Resultset。
  4. 結果映射:JDBC的查詢結果( Resultset )會被 MyBatis 的 Resultmap 或 resultType 映射為 DAO 接囗方法的返回值類型(如 POJO、Map或 List)。

MyBatis 動態 sql有什麼用?執行原理?有哪些動態 sq!?

動態SQL是在 MyBatis中根據不同的條件、需求動態生成 so!語句的一種機制。它的主要目的是提高 sql 的靈活性和複用性,在複雜的查詢或更新場景中,根據參數動態構建不同的 sqL語句,

動態 SQL的執行基於XML映射文件中定義的 SOL片段與標籤,如 if、choose、when、otherwise、where、foreach 等,這些標籤被解析,在運行時根據傳入的參數值評估,最終形成完整的 SQL 語句發送到傲數據庫

執行MyBatis 解析動態 SQL 的流程如下:

  1. 解析動態 SQL:在映射文件加載時,MyBatis 會解析 XML文件中的動態 SQL 標籤。
  2. 參數綁定:當執行 SQL語句時,MyBatis 會根據傳入的參數綁定具體的值。
  3. 生成最終 SQL:根據參數值和動態 SQL 標籤生成最終的 SQL語句。
  4. 執行 SQL:MyBatis 執行生成的 SQL語句,並返回結果。

常見動態sql:

  • if標籤允許在 SQL 中根據條件包含不同的部分
  • where 標籤智能地插入 WHERE 關鍵字,並在必要時去除多餘的 AND 或 OR。
  • foreach 標籤適用於需要遍歷列表或數組,生成重複的 SQL 片段,如批量插入或 IN 條件查詢。
  • choose, when, otherwise 標籤加一起相當於Java 中的 switch 語句,根據多個條件選擇一個執行。

Mybatis使用

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

  • 接口方法名與 SQL 映射文件中的 id 要一致。
  • 接口的全限定名要作為 xml 文件的命名空間。
  • 參數和返回值要與映射文件的配置匹配,同時通過 @Param 註解可以處理多個參數的情況。
  • 如果在 Spring 環境中需要進行 Mapper 掃描以註冊為 Bean。

Mybatis都有哪些Executor執行器?它們之間的區別是什麼?

Mybatis有三種基本的Executor執行器,SimpleExecutorReuseExecutorBatchExecutor

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中接口綁定有幾種實現方式?

  1. 通過註解綁定,在接口的方法上面加上 @Select@Update等註解裏面包含Sql語句來綁定(SQL語句比較簡單的時候,推薦註解綁定)
  2. 通過xml裏面寫SQL來綁定, 指定xml映射文件裏面的namespace必須為接口的全路徑名(SQL語句比較複雜的時候,推薦xml綁定)

xml 映射文件中,除了常見的 select、insert、update、delete 標籤之外,還有哪些標籤?

除了常見的select、insert、update和delete標籤,MyBatis的XML映射文件中還有一些其他標籤用於更復雜的操作和配置。以下是一些常見的額外標籤:

  1. resultMap: 用於定義查詢結果與Java對象之間的映射關係,可以在多個查詢中重複使用。
  2. association和collection: 用於在resultMap中定義關聯關係,用於處理一對一和一對多的關係。
  3. discriminator: 在resultMap中使用,根據不同的條件選擇不同的映射規則,用於處理繼承關係的映射。
  4. sql: 可以定義可重用的SQL片段,然後在其他地方引用。主要用於減少重複編寫SQL語句。
  5. include: 用於在SQL語句中引入外部定義的SQL片段,提高可維護性。
  6. if、choose、when、otherwise: 用於在SQL語句中進行條件判斷和邏輯控制,用於動態SQL的構建。
  7. trim、where、set: 用於在SQL語句中添加固定的SQL片段,如where和set關鍵字,用於動態的條件構建。
  8. foreach: 用於在SQL語句中進行集合迭代,適用於生成IN語句等。
  9. bind: 用於在SQL語句中聲明並綁定一個變量,可以在查詢中重複使用。
  10. cache: 用於配置二級緩存。
  11. selectKey: 用於在插入操作後獲取生成的主鍵值。
  12. insert、update、delete的flushCache、useGeneratedKeys、keyProperty屬性: 用於配置插入、更新和刪除操作的一些屬性。

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

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

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

MyBatis 是否可以映射 Enum 枚舉類?

MyBatis 可以映射枚舉類,不單可以映射枚舉類,MyBatis 可以映射任何對象到表的一列上。映射方式為自定義一個 TypeHandler ,實現 TypeHandlersetParameter()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 標籤也就可以正常解析完成了。

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

在MyBatis中,要執行模糊查詢(使用LIKE語句),可以使用SQL語句的字符串拼接或使用動態SQL來構建查詢語句。

假設你要在一個查詢中執行模糊查詢,搜索用户的用户名包含特定關鍵字的情況。

字符串拼接方式:

<select id="searchUsers" resultMap="userResultMap">
SELECT * FROM users
	WHERE username LIKE CONCAT('%', #{keyword}, '%')
</select>

在這個例子中,#{keyword}是參數佔位符,表示要搜索的關鍵字。CONCAT('%', #{keyword}, '%')用於構建模糊匹配的字符串。

動態SQL方式:

<select id="searchUsers" resultMap="userResultMap">
SELECT * FROM users
	<where>
		<if test="keyword != null">
			AND username LIKE CONCAT('%', #{keyword}, '%')
		</if>
	</where>
</select>

在這個例子中,使用了<if>標籤來創建動態條件。只有在keyword參數不為null時,才會添加AND username LIKE CONCAT('%', #{keyword}, '%')這個條件到查詢語句中。

MyBatis 自帶的連接池有了解過嗎?

MyBatis 自帶的連接池是 PooledDataSource 類實現的,是一個簡單的連接池實現,提供了連接複用和基本的資源管理功能

執行原理:

  1. 初始化連接池:PooledDataSource 會初始化一定數量的連接,放入空閒連接隊列。
  2. 獲取連接:調用 getconnection()方法時,優先從空閒隊列中獲取連接,如果空閒隊列為空,且活躍連接未達上限,則創建新連接。
  3. 回收連接:使用完畢後,通過 pushConnection()方法將連接放回空閒隊列。
  4. 失效檢測:通過 poolPingQuery 定期檢查空閒連接的可用性;失效的連接會被丟棄。

關鍵配置:

  • poolMaximumActiveConnections:最大活躍連接數
  • poolMaximumIdleConnections:最大空閒連接數。
  • poolMaximumCheckouTime:單個連接的最大佔用時間,超過時間會被強制回收
  • poolPingQuery:檢測連接可用性的 SQL。

Mybatis 如何實現一對一、一對多的關聯查詢 ?

在 MyBatis 中,實現一對一和一對多的關聯査詢主要是通過 resultMap 來完成的。MyBatis 提供了兩種方式來處理關聯關係

  • 嵌套結果映射(Nested Result Mapping)

    • 適用於一次 SQL 查詢中同時返回主表和關聯表的數據。
    • 使用<association>標籤表示一對一的關係。
    • 使用<collection>標籤表示一對多的關係。
  • 嵌套查詢(Nested Select)

    • 主查詢只查主表數據,關聯表數據通過單獨的 SQL查詢獲取。
    • 使用<association><collection>的select 屬性指定子查詢

MyBatis如何實現動態數據源切換?

在實現動態數據源切換方面,MyBatis有幾種方法,讓你能夠在不同的數據庫之間輕鬆切換。比如,你可能會在開發環境和生產環境中使用不同的數據庫。下面是一些可以考慮的方法:

  1. 我們可以通過配置文件來實現切換。可以在MyBatis的配置文件裏配置多個數據源,然後根據需要在代碼中進行切換。這就涉及到定義多個數據源的連接信息和配置,然後在代碼裏通過指定數據源的標識來選擇要使用哪個數據源。這種方法需要在配置文件中進行一些準備工作,但切換過程相對比較容易。
  2. 可以運用AOP切面編程來實現切換。通過使用面向切面編程(AOP),可以在方法調用之前進行攔截,然後根據條件來動態地切換數據源。可以創建一個切面,將切入點設定為需要切換數據源的方法,然後在切面中實現數據源切換的邏輯。這樣的做法能夠將切換邏輯和業務邏輯分隔開,有助於提高代碼的可維護性。
  3. 可以使用MyBatis提供的AbstractRoutingDataSource類。這個類允許你創建一個數據源路由器,根據特定的規則來選擇數據源。你可以繼承這個類,然後實現其中的determineCurrentLookupKey()方法,以返回當前應該使用的數據源標識。這種方式非常靈活,可以根據不同的條件來切換數據源。
  4. 使用第三方庫。例如Druid和HikariCP等。這些庫通常提供了更多的功能和配置選項,可以根據實際需求來選擇合適的庫。

MyBatis 和 MyBatis Plus 有哪些區別?

  1. MyBatis Plus 是 MyBatis 的增強工具,提供了許多開箱即用的功能和簡化的操作接口。對於單數據表的常見操作(CRUD)提供了自動化的方法,減少了 SQL 代碼的編寫,但對於複雜查詢仍需手動編寫。
  2. MyBatis 不提供內置的分頁功能,通常需要使用第三方分頁插件(如 PageHelper)或手動編寫 SQL。MyBatis Plus 內置了分頁功能,使用非常簡單。

就可以看成 MyBatis 能實現的 MyBatis Plus都實現了,是增強版工具

user avatar jixingsuiyuan 頭像 vksfeng 頭像 wuhuacong 頭像 haitangyijiu_640e92f08aef6 頭像
4 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.