榮耀推出多主攝融合計算攝影,趙明稱拍照效果相比蘋果 iPhone 13 系列更優
簡介
什麼是快取
- 存在記憶體中的臨時資料。
- 將使用者經常查詢的資料放在快取(記憶體)中,使用者去查詢資料就不用從磁碟上(關係型資料庫資料檔案)查詢,轉從快取中查詢,從而提高查詢效率,解決了高併發系統的效能問題。
為什麼使用快取
- 減少和資料庫的互動次數,減少系統開銷,提高系統效率。
什麼樣的資料適合使用快取
- 經常查詢並且不經常改變的資料。
Mybatis快取
-
MyBatis 包含一個非常強大的查詢快取特性,它可以非常方便地定製和配置快取。
-
MyBatis 系統中預設定義了兩級快取:一級快取和二級快取
-
- 預設情況下,只有一級快取開啟。( SqlSession 級別的快取,也稱為本地快取)
- 二級快取需要手動開啟和配置,他是基於 namespace 級別的快取。
- 為了提高擴充套件性,MyBatis 定義了快取介面 Cache。我們可以通過實現 Cache 介面來自定義二級快取。
一級快取
一級快取也叫本地快取:
- 與資料庫同一次會話期間查詢到的資料會放在本地快取中。
- 以後如果需要獲取相同的資料,直接從快取中拿,沒必須再去查詢資料庫;
測試
先在mybatis中加入日誌,方便測試結果。
@Test public void oneCache() { SqlSession sqlSession = MybatisUtils.getSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user1 = userMapper.selectOne(1); System.out.println(user1); User user2 = userMapper.selectOne(1); System.out.println(user2); System.out.println(user1 == user2); sqlSession.close(); }
列印結果
Opening JDBC Connection Created connection 1806431167. Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6babf3bf] ==> Preparing: select id, name, pwd from user where id = ? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 狂神, 123456 <== Total: 1 User(id=1, name=狂神, pwd=123456) User(id=1, name=狂神, pwd=123456) true
可以看出,只進行了一次資料庫查詢,第二次查詢沒走資料庫,直接取的快取資料,所以兩個物件相等。
一級快取失效的四種情況
一級快取是SqlSession級別的快取,是一直開啟的,我們關閉不了它。
要想讓一級快取失效,只能是不使用當前的一級快取,效果就是,還需要再向資料庫中發起一次查詢請求。
sqlSession 不同
@Test
public void oneCache() {
SqlSession sqlSession1 = MybatisUtils.getSession();
SqlSession sqlSession2 = MybatisUtils.getSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper1.selectOne(1);
System.out.println(user1);
User user2 = userMapper2.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession1.close();
sqlSession2.close();
}
列印結果
Opening JDBC Connection
Created connection 1806431167.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6babf3bf]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
Opening JDBC Connection
Created connection 1128948651.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@434a63ab]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
false
可以很明顯的看出,進行了兩次查詢,所以這裡沒有使用到快取。
由此可以得出結論:每個sqlSession中的快取相互獨立。
sqlSession 相同,查詢條件不同
@Test
public void oneCache() {
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.selectOne(1);
System.out.println(user1);
User user2 = userMapper.selectOne(2);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}
列印結果
Opening JDBC Connection
Created connection 1806431167.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6babf3bf]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 2(Integer)
<== Columns: id, name, pwd
<== Row: 2, 張三, abcdef
<== Total: 1
User(id=2, name=張三, pwd=abcdef)
false
發現進行了兩次查詢。
很容易理解,因為當前快取中,不存在這個資料。
sqlSession 相同,兩次查詢之間執行了增刪改操作
測試
@Test
public void oneCache() {
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.selectOne(1);
System.out.println(user1);
userMapper.delete(14);
User user2 = userMapper.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}
列印結果:在中間執行了刪除操作後,第二次查詢也執行了。
結論:因為增刪改操作可能會對當前資料產生影響,為嚴謹考慮,會再執行一次查詢操作。
sqlSession 相同,手動清除一級快取
@Test
public void oneCache() {
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.selectOne(1);
System.out.println(user1);
sqlSession.clearCache();
User user2 = userMapper.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}
列印結果:執行了兩次查詢,快取失效。
結論:一級快取就是一個 map,清除了之後就沒有了,自然會再進行一次查詢。
二級快取
-
二級快取也叫全域性快取,一級快取作用域太低了,所以誕生了二級快取。
-
基於 namespace 級別的快取,一個名稱空間,對應一個二級快取。
-
工作機制:
-
- 一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中。
- 如果當前會話關閉了,這個會話對應的一級快取就沒了。
- 我們想要的是,會話關閉了,一級快取中的資料被儲存到二級快取中。
- 新的會話查詢資訊,就可以從二級快取中獲取內容。
- 不同的 mapper 查出的資料會放在自己對應的快取( map )中。
使用步驟
- 開啟全域性快取 【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
- 去每個 mapper.xml 中配置使用二級快取,這個配置非常簡單。
<cache/>
這裡也可以自定義配置,比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個更高階的配置建立了一個 FIFO 快取,每隔 60 秒重新整理,最多可以儲存結果物件或列表的 512 個引用,而且返回的物件被認為是隻讀的,因此對它們進行修改可能會在不同執行緒中的呼叫者產生衝突。
eviction(清除策略)
可用的清除策略有:
LRU
– 最近最少使用:移除最長時間不被使用的物件。FIFO
– 先進先出:按物件進入快取的順序來移除它們。SOFT
– 軟引用:基於垃圾回收器狀態和軟引用規則移除物件。WEAK
– 弱引用:更積極地基於垃圾收集器狀態和弱引用規則移除物件。
預設的清除策略是 LRU。
flushInterval(重新整理間隔)
-
可以被設定為任意的正整數,設定的值應該是一個以毫秒為單位的合理時間量。
-
預設情況是不設定,也就是沒有重新整理間隔,快取僅僅會在呼叫語句時重新整理。
size(引用數目)
-
可以被設定為任意正整數,要注意欲快取物件的大小和執行環境中可用的記憶體資源。
-
預設值是 1024。
readOnly(只讀)
-
可以被設定為 true 或 false。
-
只讀的快取會給所有呼叫者返回快取物件的相同例項,因此這些物件不能被修改。這就提供了可觀的效能提升。
-
可讀寫的快取會(通過序列化)返回快取物件的拷貝。 速度上會慢一些,但是更安全,因此預設值是 false。
二級快取是事務性的。這意味著,當 SqlSession 完成並提交時,或是完成並回滾,但沒有執行 flushCache=true 的 insert/delete/update 語句時,快取會獲得更新。
- 程式碼測試
- 所有的實體類先實現序列化介面
- 測試程式碼
@Test
public void TwoCache(){
SqlSession sqlSession1 = MybatisUtils.getSession();
SqlSession sqlSession2 = MybatisUtils.getSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper1.selectOne(1);
System.out.println(user1);
sqlSession1.close();
User user2 = userMapper2.selectOne(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession2.close();
}
列印結果
Opening JDBC Connection
Created connection 1048855692.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3e84448c]
==> Preparing: select id, name, pwd from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 狂神, 123456
<== Total: 1
User(id=1, name=狂神, pwd=123456)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3e84448c]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3e84448c]
Returned connection 1048855692 to pool.
As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
Cache Hit Ratio [cn.sail.mapper.UserMapper]: 0.5
User(id=1, name=狂神, pwd=123456)
false
由此可以看出,雖然前一個連線被關閉了,但由於開啟了二級快取,在查詢同一個 Mapper 的情況下,第二次建立的連線也使用了前一次連線快取中的資料,但此時物件不一樣了。
結論
- 只要開啟了二級快取,我們在同一個 Mapper 中的查詢,可以在二級快取中拿到資料。
- 查出的資料都會被預設先放在一級快取中。
- 只有會話提交或者關閉以後,一級快取中的資料才會轉到二級快取中。
快取原理圖
EhCache
第三方快取實現:EhCache
Ehcache 是一種廣泛使用的 java 分散式快取,用於通用快取。
使用步驟
- 要引入依賴的jar包。
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
- 在mapper.xml中使用對應的快取即可
<mapper namespace="org.acme.FooMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>
- 編寫ehcache.xml檔案,如果在載入時未找到/ehcache.xml資源或出現問題,則將使用預設配置。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:為快取路徑,ehcache分為記憶體和磁碟兩級,此屬性定義磁碟的快取位置。引數解釋如下:
user.home – 使用者主目錄
user.dir – 使用者當前工作目錄
java.io.tmpdir – 預設臨時檔案路徑
-->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
<!--
defaultCache:預設快取策略,當ehcache找不到定義的快取時,則使用這個快取策略。只能定義一個。
-->
<!--
name:快取名稱。
maxElementsInMemory:快取最大數目
maxElementsOnDisk:硬碟最大快取個數。
eternal:物件是否永久有效,一但設定了,timeout將不起作用。
overflowToDisk:是否儲存到磁碟,當系統當機時
timeToIdleSeconds:設定物件在失效前的允許閒置時間(單位:秒)。僅當eternal=false物件不是永久有效時使用,可選屬性,預設值是0,也就是可閒置時間無窮大。
timeToLiveSeconds:設定物件在失效前允許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false物件不是永久有效時使用,預設是0.,也就是物件存活時間無窮大。
diskPersistent:是否快取虛擬機器重啟期資料 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:這個引數設定DiskStore(磁碟快取)的快取區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區。
diskExpiryThreadIntervalSeconds:磁碟失效執行緒執行時間間隔,預設是120秒。
memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
clearOnFlush:記憶體數量最大時是否清除。
memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,預設策略)、FIFO(先進先出)、LFU(最少訪問次數)。
FIFO,first in first out,這個是大家最熟的,先進先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,快取的元素有一個hit屬性,hit值最小的將會被清出快取。
LRU,Least Recently Used,最近最少使用的,快取的元素有一個時間戳,當快取容量滿了,而又需要騰出地方來快取新的元素的時候,那麼現有快取元素中時間戳離當前時間最遠的元素將被清出快取。
-->
</ehcache>