MyBatis緩存機(jī)制深度解析

2025-01-10 11:18 更新

MyBatis 是一個(gè)流行的 Java 持久層框架,它提供了對數(shù)據(jù)庫的簡單操作和映射。MyBatis 的緩存機(jī)制是其核心特性之一,它可以幫助開發(fā)者提高應(yīng)用程序的性能,通過減少對數(shù)據(jù)庫的直接訪問次數(shù)來降低數(shù)據(jù)庫的負(fù)載。

1. MyBatis 緩存介紹

默認(rèn)緩存行為

  • 局部的 session 緩存:MyBatis 默認(rèn)開啟的緩存是局部的 session 緩存,這意味著每個(gè) MyBatis session 都會有自己的緩存,這個(gè)緩存僅在當(dāng)前 session 內(nèi)有效。它主要用于處理循環(huán)依賴和提升性能。

二級緩存(全局緩存)

  • 開啟二級緩存:要開啟 MyBatis 的二級緩存,需要在 SQL 映射文件中添加 <cache/> 標(biāo)簽。這將允許跨多個(gè) session 共享緩存。

緩存的基本屬性

  • select 語句緩存:所有 select 語句的結(jié)果都會被緩存。
  • 刷新機(jī)制:insert, update 和 delete 語句會觸發(fā)緩存的刷新。
  • LRU 算法:默認(rèn)使用最近最少使用(Least Recently Used)算法來決定哪些緩存項(xiàng)應(yīng)該被移除。
  • 無時(shí)間刷新:默認(rèn)情況下,緩存不會根據(jù)時(shí)間間隔自動刷新。
  • 引用數(shù)量:默認(rèn)情況下,緩存可以存儲 1024 個(gè)引用。
  • 可讀/可寫:默認(rèn)情況下,緩存是可讀寫的,這意味著緩存的對象可以被調(diào)用者修改,而不會干擾其他調(diào)用者或線程。

高級緩存配置

  • eviction(回收策略):可以設(shè)置不同的回收策略,如 LRU、FIFO、SOFT 和 WEAK。
    • LRU:最近最少使用,移除最長時(shí)間不被使用的對象。
    • FIFO:先進(jìn)先出,按對象進(jìn)入緩存的順序移除。
    • SOFT:軟引用,基于垃圾收集器狀態(tài)和軟引用規(guī)則移除對象。
    • WEAK:弱引用,更積極地移除對象,基于垃圾收集器狀態(tài)和弱引用規(guī)則。
  • flushInterval(刷新間隔):可以設(shè)置一個(gè)時(shí)間間隔,以毫秒為單位,緩存會在該時(shí)間間隔后自動刷新。
  • size(引用數(shù)目):可以設(shè)置緩存中存儲的對象或列表的引用數(shù)量,需要根據(jù)可用內(nèi)存資源來決定。
  • readOnly(只讀):設(shè)置為 true 時(shí),所有調(diào)用者將獲得緩存對象的相同實(shí)例,這些對象不能被修改,提供了性能優(yōu)勢。設(shè)置為 false 時(shí),緩存對象可以被修改,但會返回對象的拷貝,這會降低性能。

配置示例

以下是一個(gè)配置示例,展示了如何使用 <cache> 標(biāo)簽來自定義緩存行為:

  1. <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

  • eviction="FIFO":使用先進(jìn)先出的策略來管理緩存。
  • flushInterval="60000":每 60 秒刷新一次緩存。
  • size="512":緩存可以存儲 512 個(gè)引用。
  • readOnly="true":緩存對象是只讀的,不能被修改。

2. 四種回收策略的原理分析

1. LRU

LRU(Least Recently Used)算法是一種常見的緩存回收策略,用于決定哪些數(shù)據(jù)應(yīng)該被從緩存中移除以騰出空間給新數(shù)據(jù)。這種策略基于一個(gè)簡單的理念:如果數(shù)據(jù)在一段時(shí)間內(nèi)沒有被使用,那么它在未來被使用的可能性也相對較低。下面詳細(xì)介紹LRU算法的實(shí)現(xiàn)原理:

數(shù)據(jù)結(jié)構(gòu)

LRU算法通常使用以下兩種數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn):

  1. 哈希表(Hash Map):用于快速定位緩存項(xiàng),O(1)時(shí)間復(fù)雜度。
  2. 雙向鏈表(Doubly Linked List):用于維護(hù)緩存項(xiàng)的使用順序,允許快速添加和刪除節(jié)點(diǎn)。

工作原理

  1. 緩存訪問:當(dāng)緩存被訪問時(shí)(無論是讀取還是寫入),該緩存項(xiàng)會被視為“最近使用”的,并移動到雙向鏈表的頭部(最近使用的位置)。
  2. 緩存添加:當(dāng)新數(shù)據(jù)被添加到緩存時(shí),如果緩存未滿,新數(shù)據(jù)會被添加到鏈表頭部。如果緩存已滿,則鏈表尾部的數(shù)據(jù)(最不常用的數(shù)據(jù))會被移除,新數(shù)據(jù)添加到頭部。
  3. 緩存淘汰:當(dāng)緩存達(dá)到容量上限時(shí),鏈表尾部的數(shù)據(jù)(最長時(shí)間未被使用的數(shù)據(jù))會被移除,為新數(shù)據(jù)騰出空間。

具體實(shí)現(xiàn)步驟

  1. 初始化:創(chuàng)建一個(gè)空的哈希表和一個(gè)空的雙向鏈表。
  2. 訪問緩存
    • 檢查數(shù)據(jù)是否在哈希表中:
      • 如果在,更新該數(shù)據(jù)在鏈表中的位置(移動到頭部),并返回?cái)?shù)據(jù)。
      • 如果不在,從數(shù)據(jù)源獲取數(shù)據(jù),添加到鏈表頭部,并在哈希表中創(chuàng)建條目。
  3. 添加數(shù)據(jù)
    • 如果緩存未滿,直接添加數(shù)據(jù)到鏈表頭部,并在哈希表中創(chuàng)建條目。
    • 如果緩存已滿,先從鏈表尾部移除最不常用的數(shù)據(jù),并從哈希表中刪除相應(yīng)條目,然后添加新數(shù)據(jù)到鏈表頭部。
  4. 維護(hù)順序:每次訪問或添加數(shù)據(jù)時(shí),都需要更新數(shù)據(jù)在雙向鏈表中的位置,確保最近使用的數(shù)據(jù)總是在鏈表頭部。

性能考慮

  • 時(shí)間復(fù)雜度:LRU算法在訪問和添加數(shù)據(jù)時(shí)都能保持O(1)的時(shí)間復(fù)雜度,這得益于哈希表和雙向鏈表的結(jié)合使用。
  • 空間復(fù)雜度:主要取決于緩存的大小,即存儲的數(shù)據(jù)量。

應(yīng)用場景

LRU算法廣泛應(yīng)用于操作系統(tǒng)的頁面置換算法、Web服務(wù)器的圖片或資源緩存、數(shù)據(jù)庫查詢結(jié)果緩存等領(lǐng)域,以提高系統(tǒng)性能和響應(yīng)速度。

示例代碼(偽代碼)

  1. class LRUCache {
  2. HashMap<Integer, Node> map;
  3. DoublyLinkedList cacheList;
  4. int capacity;
  5. public LRUCache(int capacity) {
  6. this.capacity = capacity;
  7. this.map = new HashMap<>();
  8. this.cacheList = new DoublyLinkedList();
  9. }
  10. public get(int key) {
  11. if (map.containsKey(key)) {
  12. Node node = map.get(key);
  13. cacheList.moveToHead(node); // Move to head to mark as recently used
  14. return node.value;
  15. }
  16. return -1; // Not found
  17. }
  18. public put(int key, int value) {
  19. if (map.containsKey(key)) {
  20. Node node = map.get(key);
  21. node.value = value;
  22. cacheList.moveToHead(node);
  23. } else {
  24. Node newNode = new Node(key, value);
  25. map.put(key, newNode);
  26. cacheList.addHead(newNode);
  27. if (map.size() > capacity) {
  28. Node tail = cacheList.removeTail();
  29. map.remove(tail.key);
  30. }
  31. }
  32. }
  33. }
  34. class Node {
  35. int key;
  36. int value;
  37. Node prev;
  38. Node next;
  39. public Node(int key, int value) {
  40. this.key = key;
  41. this.value = value;
  42. }
  43. }
  44. class DoublyLinkedList {
  45. Node head;
  46. Node tail;
  47. public addHead(Node node) {
  48. // Add node to the head of the list
  49. }
  50. public removeTail() {
  51. // Remove node from the tail of the list and return it
  52. }
  53. public moveToHead(Node node) {
  54. // Move node to the head of the list
  55. }
  56. }

以上是對LRU算法實(shí)現(xiàn)原理的詳細(xì)介紹,包括其數(shù)據(jù)結(jié)構(gòu)、工作原理、具體實(shí)現(xiàn)步驟以及性能和應(yīng)用場景。

2. FIFO

FIFO(First In, First Out)算法是一種簡單的緩存回收策略,它按照數(shù)據(jù)進(jìn)入緩存的順序來決定哪些數(shù)據(jù)應(yīng)該被移除。這種策略的核心思想是:最先進(jìn)入緩存的數(shù)據(jù)將會是最先被移除的數(shù)據(jù)。FIFO算法在實(shí)現(xiàn)上相對簡單,但可能不如LRU(最近最少使用)算法那樣高效,特別是在某些訪問模式下。以下是FIFO算法的實(shí)現(xiàn)原理和詳細(xì)步驟:

數(shù)據(jù)結(jié)構(gòu)

FIFO算法通常使用以下數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn):

  1. 隊(duì)列(Queue):用于維護(hù)緩存項(xiàng)的順序,確保最先進(jìn)入的數(shù)據(jù)最先被移除。
  2. 哈希表(Hash Map):用于快速定位緩存項(xiàng),提供O(1)時(shí)間復(fù)雜度的訪問。

工作原理

  1. 緩存訪問:當(dāng)緩存被訪問時(shí)(無論是讀取還是寫入),該緩存項(xiàng)會被視為“最近使用”的。
  2. 緩存添加
    • 如果緩存未滿,新數(shù)據(jù)會被添加到隊(duì)列的尾部。
    • 如果緩存已滿,隊(duì)列頭部的數(shù)據(jù)會被移除,新數(shù)據(jù)添加到隊(duì)列尾部。
  3. 緩存淘汰:當(dāng)緩存達(dá)到容量上限時(shí),隊(duì)列頭部的數(shù)據(jù)(最先進(jìn)入的數(shù)據(jù))會被移除,為新數(shù)據(jù)騰出空間。

具體實(shí)現(xiàn)步驟

  1. 初始化:創(chuàng)建一個(gè)空的隊(duì)列和一個(gè)空的哈希表。
  2. 訪問緩存
    • 檢查數(shù)據(jù)是否在哈希表中:
      • 如果在,返回?cái)?shù)據(jù),但不需要移動數(shù)據(jù)在隊(duì)列中的位置。
      • 如果不在,從數(shù)據(jù)源獲取數(shù)據(jù),添加到隊(duì)列尾部,并在哈希表中創(chuàng)建條目。
  3. 添加數(shù)據(jù)
    • 如果緩存未滿,直接添加數(shù)據(jù)到隊(duì)列尾部,并在哈希表中創(chuàng)建條目。
    • 如果緩存已滿,先從隊(duì)列頭部移除最舊的數(shù)據(jù),并從哈希表中刪除相應(yīng)條目,然后添加新數(shù)據(jù)到隊(duì)列尾部。
  4. 維護(hù)順序:每次添加新數(shù)據(jù)時(shí),都需要更新隊(duì)列和哈希表。

性能考慮

  • 時(shí)間復(fù)雜度:FIFO算法在訪問和添加數(shù)據(jù)時(shí)都能保持O(1)的時(shí)間復(fù)雜度,這得益于哈希表的使用。
  • 空間復(fù)雜度:主要取決于緩存的大小,即存儲的數(shù)據(jù)量。

應(yīng)用場景

FIFO算法由于其簡單性,適用于那些對緩存一致性要求不高的場景。它可能不適用于那些頻繁訪問某些數(shù)據(jù)的應(yīng)用程序,因?yàn)檫@些數(shù)據(jù)可能會被錯(cuò)誤地移除。

示例代碼(偽代碼)

  1. class FIFOCache {
  2. HashMap<Integer, Integer> map;
  3. LinkedList<Integer> queue;
  4. int capacity;
  5. public FIFOCache(int capacity) {
  6. this.capacity = capacity;
  7. this.map = new HashMap<>();
  8. this.queue = new LinkedList<>();
  9. }
  10. public get(int key) {
  11. if (map.containsKey(key)) {
  12. return map.get(key);
  13. }
  14. return -1; // Not found
  15. }
  16. public put(int key, int value) {
  17. if (map.containsKey(key)) {
  18. // Key already exists, update the value and remove the key from the queue
  19. queue.remove(map.get(key));
  20. map.put(key, value);
  21. queue.addLast(key);
  22. } else {
  23. if (map.size() >= capacity) {
  24. // Cache is full, remove the oldest item
  25. int oldestKey = queue.removeFirst();
  26. map.remove(oldestKey);
  27. }
  28. // Add new item
  29. map.put(key, value);
  30. queue.addLast(key);
  31. }
  32. }
  33. }
  34. class LinkedList {
  35. Node head;
  36. Node tail;
  37. public addLast(int value) {
  38. // Add value to the end of the list
  39. }
  40. public removeFirst() {
  41. // Remove the first element from the list and return it
  42. }
  43. }
  44. class Node {
  45. int value;
  46. Node next;
  47. public Node(int value) {
  48. this.value = value;
  49. }
  50. }

以上是對FIFO算法實(shí)現(xiàn)原理的詳細(xì)介紹,包括其數(shù)據(jù)結(jié)構(gòu)、工作原理、具體實(shí)現(xiàn)步驟以及性能和應(yīng)用場景。FIFO算法雖然簡單,但在某些情況下可能不如LRU算法有效,特別是在數(shù)據(jù)訪問模式不均勻的情況下。

3. SOFT

SOFT(軟引用)是一種緩存回收策略,它在 Java 中通過 java.lang.ref.SoftReference 類實(shí)現(xiàn)。軟引用允許對象在內(nèi)存不足時(shí)被垃圾收集器回收,但只要內(nèi)存足夠,這些對象就可以繼續(xù)存活。這種策略特別適用于緩存機(jī)制,因?yàn)樗梢栽诓挥绊憫?yīng)用程序功能的情況下,動態(tài)地釋放內(nèi)存資源。以下是 SOFT 緩存策略的實(shí)現(xiàn)原理和詳細(xì)步驟:

工作原理

  1. 軟引用:軟引用是一種比強(qiáng)引用(Strong Reference)弱,但比弱引用(Weak Reference)強(qiáng)的引用類型。軟引用關(guān)聯(lián)的對象在內(nèi)存不足時(shí)可以被垃圾收集器回收,但只要內(nèi)存足夠,它們就會繼續(xù)存活。
  2. 垃圾收集器:Java 的垃圾收集器會定期檢查內(nèi)存使用情況,并在內(nèi)存不足時(shí)嘗試回收軟引用對象。
  3. 緩存管理:使用軟引用實(shí)現(xiàn)的緩存會在內(nèi)存不足時(shí)自動釋放緩存對象,從而為新對象騰出空間。

具體實(shí)現(xiàn)步驟

  1. 初始化緩存:創(chuàng)建一個(gè)緩存容器,如 HashMap,用于存儲鍵和軟引用對象的映射。
  2. 訪問緩存
    • 當(dāng)訪問緩存時(shí),首先檢查軟引用是否仍然有效(即其關(guān)聯(lián)的對象是否已被回收)。
    • 如果軟引用有效,返回其關(guān)聯(lián)的對象。
    • 如果軟引用無效,說明對象已被回收,可以重新從數(shù)據(jù)源獲取數(shù)據(jù),并創(chuàng)建新的軟引用。
  3. 添加數(shù)據(jù)
    • 當(dāng)添加新數(shù)據(jù)到緩存時(shí),使用 SoftReference 包裝該對象,并將其存儲在緩存容器中。
    • 由于軟引用的特性,如果內(nèi)存不足,這些對象可能會被垃圾收集器回收。
  4. 內(nèi)存回收:當(dāng)系統(tǒng)內(nèi)存不足時(shí),垃圾收集器會嘗試回收軟引用對象。這使得緩存可以自動調(diào)整大小,釋放不再需要的內(nèi)存。

性能考慮

  • 時(shí)間復(fù)雜度:訪問和添加數(shù)據(jù)的時(shí)間復(fù)雜度通常為 O(1),因?yàn)?HashMap 提供了快速的鍵值對查找。
  • 空間復(fù)雜度:緩存的大小取決于緩存對象的數(shù)量和每個(gè)對象的大小,但軟引用允許在內(nèi)存不足時(shí)自動回收對象,從而動態(tài)調(diào)整緩存大小。

應(yīng)用場景

軟引用緩存適用于以下場景:

  • 內(nèi)存敏感的應(yīng)用程序:在內(nèi)存資源有限的設(shè)備上,如移動設(shè)備或嵌入式系統(tǒng),軟引用緩存可以動態(tài)地釋放內(nèi)存。
  • 大對象緩存:對于占用大量內(nèi)存的對象,如圖片或大型文檔,軟引用緩存可以在內(nèi)存不足時(shí)自動釋放這些對象。
  • 可有可無的緩存:在某些情況下,緩存數(shù)據(jù)的丟失不會對應(yīng)用程序的功能產(chǎn)生重大影響,軟引用緩存是一個(gè)很好的選擇。

示例代碼(Java)

  1. import java.lang.ref.SoftReference;
  2. import java.util.HashMap;
  3. public class SoftReferenceCache<K, V> {
  4. private HashMap<K, SoftReference<V>> cache = new HashMap<>();
  5. public V get(K key) {
  6. SoftReference<V> ref = cache.get(key);
  7. if (ref != null) {
  8. V value = ref.get();
  9. if (value != null) {
  10. return value;
  11. }
  12. // SoftReference has been cleared, remove it from the cache
  13. cache.remove(key);
  14. }
  15. return null;
  16. }
  17. public void put(K key, V value) {
  18. cache.put(key, new SoftReference<>(value));
  19. }
  20. }

在這個(gè)示例中,SoftReferenceCache 使用 HashMap 存儲鍵和軟引用對象的映射。當(dāng)訪問緩存時(shí),首先檢查軟引用是否有效。如果軟引用無效,說明對象已被回收,可以重新從數(shù)據(jù)源獲取數(shù)據(jù),并創(chuàng)建新的軟引用。

小結(jié)

SOFT 緩存策略通過使用軟引用來實(shí)現(xiàn)緩存對象的自動回收,從而在內(nèi)存不足時(shí)動態(tài)地釋放內(nèi)存資源。這種策略特別適用于內(nèi)存敏感的應(yīng)用程序,或者那些緩存數(shù)據(jù)丟失不會對應(yīng)用程序功能產(chǎn)生重大影響的場景。

4. WEAK

WEAK(弱引用)是一種比軟引用(Soft Reference)更弱的引用類型,它允許對象在下一次垃圾收集時(shí)被回收,無論內(nèi)存是否足夠。在 Java 中,弱引用是通過 java.lang.ref.WeakReference 類實(shí)現(xiàn)的。弱引用通常用于實(shí)現(xiàn)緩存,其中對象的生命周期不需要超過引用本身的生命周期。以下是 WEAK 緩存策略的實(shí)現(xiàn)原理和詳細(xì)步驟:

工作原理

  1. 弱引用:弱引用是一種對對象的引用,它不會阻止垃圾收集器回收其引用的對象。這意味著只要沒有其他的強(qiáng)引用指向該對象,對象就可以被垃圾收集器回收。
  2. 垃圾收集器:Java 的垃圾收集器會定期執(zhí)行,當(dāng)它發(fā)現(xiàn)某個(gè)對象只被弱引用所引用時(shí),就會回收該對象占用的內(nèi)存。
  3. 緩存管理:使用弱引用實(shí)現(xiàn)的緩存允許對象在不再被使用時(shí)被快速回收,即使內(nèi)存尚未不足。

具體實(shí)現(xiàn)步驟

  1. 初始化緩存:創(chuàng)建一個(gè)緩存容器,如 HashMap,用于存儲鍵和弱引用對象的映射。
  2. 訪問緩存
    • 當(dāng)訪問緩存時(shí),首先檢查弱引用是否仍然有效(即其關(guān)聯(lián)的對象是否已被回收)。
    • 如果弱引用有效,返回其關(guān)聯(lián)的對象。
    • 如果弱引用無效,說明對象已被回收,可以重新從數(shù)據(jù)源獲取數(shù)據(jù),并創(chuàng)建新的弱引用。
  3. 添加數(shù)據(jù)
    • 當(dāng)添加新數(shù)據(jù)到緩存時(shí),使用 WeakReference 包裝該對象,并將其存儲在緩存容器中。
    • 由于弱引用的特性,這些對象可能會在下一次垃圾收集時(shí)被回收。
  4. 內(nèi)存回收:當(dāng)垃圾收集器執(zhí)行時(shí),它會檢查所有弱引用,并回收那些只被弱引用的對象。

性能考慮

  • 時(shí)間復(fù)雜度:訪問和添加數(shù)據(jù)的時(shí)間復(fù)雜度通常為 O(1),因?yàn)?HashMap 提供了快速的鍵值對查找。
  • 空間復(fù)雜度:緩存的大小取決于緩存對象的數(shù)量和每個(gè)對象的大小,但由于弱引用允許對象在下一次垃圾收集時(shí)被回收,因此緩存不會長時(shí)間占用大量內(nèi)存。

應(yīng)用場景

弱引用緩存適用于以下場景:

  • 內(nèi)存敏感的應(yīng)用程序:在內(nèi)存資源有限的設(shè)備上,如移動設(shè)備或嵌入式系統(tǒng),弱引用緩存可以快速釋放內(nèi)存。
  • 臨時(shí)對象緩存:對于只在特定時(shí)間內(nèi)需要的對象,使用弱引用緩存可以確保這些對象在不再需要時(shí)迅速被回收。
  • 可丟棄的緩存:在某些情況下,緩存數(shù)據(jù)的丟失不會對應(yīng)用程序的功能產(chǎn)生重大影響,弱引用緩存是一個(gè)很好的選擇。

示例代碼(Java)

  1. import java.lang.ref.WeakReference;
  2. import java.util.HashMap;
  3. public class WeakReferenceCache<K, V> {
  4. private HashMap<K, WeakReference<V>> cache = new HashMap<>();
  5. public V get(K key) {
  6. WeakReference<V> ref = cache.get(key);
  7. if (ref != null) {
  8. V value = ref.get();
  9. if (value != null) {
  10. return value;
  11. }
  12. // WeakReference has been cleared, remove it from the cache
  13. cache.remove(key);
  14. }
  15. return null;
  16. }
  17. public void put(K key, V value) {
  18. cache.put(key, new WeakReference<>(value));
  19. }
  20. }

在這個(gè)示例中,WeakReferenceCache 使用 HashMap 存儲鍵和弱引用對象的映射。當(dāng)訪問緩存時(shí),首先檢查弱引用是否有效。如果弱引用無效,說明對象已被回收,可以重新從數(shù)據(jù)源獲取數(shù)據(jù),并創(chuàng)建新的弱引用。

小結(jié)

WEAK 緩存策略通過使用弱引用來實(shí)現(xiàn)緩存對象的快速回收,這對于內(nèi)存敏感的應(yīng)用程序或臨時(shí)對象的緩存非常有用。這種策略允許應(yīng)用程序在不犧牲內(nèi)存的情況下,臨時(shí)存儲和管理數(shù)據(jù)對象。

最后

MyBatis 的緩存機(jī)制非常靈活,可以通過簡單的配置來滿足不同的性能需求。合理地使用緩存可以顯著提高應(yīng)用程序的性能,尤其是在處理大量數(shù)據(jù)庫查詢時(shí)。然而,開發(fā)者需要注意緩存的一致性和并發(fā)問題,特別是在使用可讀寫緩存時(shí)。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號