【MyBatis 二級快取】
概述:一級快取作用域為同一個SqlSession物件,而二級快取用來解決一級快取不能誇會話共享,作用範圍是namespace級,可以被多個SqlSession共享(只要是同一個介面方法的相同方法,都可同享)。
- MyBatis預設支援一級二級快取。在沒有任何配置情況下,預設開啟一級快取,MyBatis 的一級快取是在會話(SqlSession)層面進行快取的;
- mybatis的二級快取預設也是開啟的,但由於他的作用域是namespace,所以還需要在mapper.xml中開啟才能生效;
- 快取優先順序:通過mybatis發起的查詢,作用順序為:二級快取->一級快取->資料庫 ,其中一二級快取不為空將直接返回結果,否則查詢資料庫;
- 快取失效:當在一個快取作用域中發生了update、insert、delete 操作後,將會觸發快取失效,下一次查詢將命中資料庫,從而保證不會查到髒資料;
- MyBatis一級快取範圍是SqlSession內部。二級快取為namespace,有多個SqlSession或者分散式的環境下,資料庫寫操作會引起髒資料,建議設定快取級別為Statement,即進行如下配置
1、【快取解析說明】
有多個SqlSession或者分散式的環境下,資料庫寫操作會引起髒資料,建議實際開發應用中設定快取級別為Statement(工程pom檔案中)。如下:
解釋:Executor為頂級介面,SimpleExecutor、BatchExecutor和ReuseExecutor是具體元件實現類,而CachingExecutor是具體的裝飾器。具體元件實現類有一個父類BaseExecutor,而這個父類是一個模板模式的典型應用,一級快取的操作都在這個類中實現,具體的運算元據庫的功能子類實現。客戶端通過工廠實現呼叫配置檔案最後呼叫資料讀取BaseExecutor中Query方法,首先快取物件,快取存在直接從快取中獲得,不存在查詢資料後再寫入快取。
(圖一):操作快取業務圖
(圖二):類關係圖
二級快取開啟步驟
A、工程配置檔案中增加二級快取配置資訊
B、xxxMapper.xml檔案中開啟全域性二級快取支援,如果不在某一個Mapper.xml檔案中開啟cache就不存在二級快取。
<!-- 宣告此namespace開啟二級快取,MyBatis自帶的二級快取 --> <cache/>
【配置說明】:
A、配置後該namespace下所有 select 語句查詢結果將會被快取;
B、覆蓋檔案的所有 insert、update 和 delete 語句會重新整理快取;
C、快取預設使用最近最少使用演算法清除不需要快取;
D、快取預設不配置定時重新整理;
E、快取預設儲存1024個引用;
【cache可以設定屬性,舉例說明】
<cache eviction="FIFO" flushInterval="60000" /* 每隔 60 秒重新整理, (重新整理間隔)屬性可以被設定為任意的正整數,設定的值應該是一個以毫秒為單位的合理時間量。 預設情況是不設定,也就是沒有重新整理間隔,快取僅僅會在呼叫語句時重新整理 */ size="2048" /* 最多可以儲存結果物件或列表的 2048 個引用, ,而且返回的物件被認為是隻讀因此對它們進行修改可能會在不同執行緒中的呼叫者產生衝突 */ readOnly="true"/> /* readOnly(只讀)屬性可以被設定為 true 或 false。只讀的快取會給所有呼叫者返回快取物件的相同例項。 因此這些物件不能被修改。這就提供了可觀的效能提升。而可讀寫的快取會(通過序列化)返回快取物件的拷貝。 速度上會慢一些,但是更安全,因此預設值是 false */
[清除策略]:
LRU :最近最少使用:移除最長時間不被使用的物件;
FIFO :先進先出:按物件進入快取的順序來移除它們;
SOFT :軟引用:基於垃圾回收器狀態和軟引用規則移除物件;
WEAK :弱引用:更積極地基於垃圾收集器狀態和弱引用規則移除物件;
C、二級快取必然會引起髒讀,實時性較高的操作可單獨關閉關閉二級快取(某一查詢禁用二級快取)
<select id="queryPersonById" parameterType="int" resultType="com.cache.bean.Person" useCache="false"/>
D、清理二級快取
二級快取清理與一級方法相同,使用commit()(執行增、刪、改時會執行commit操作,會清理掉快取,設計這個機制原因是為了防止髒讀。注意:二級快取中commit不能是查詢本身的commit); 在select標籤中,增加屬性flushCache=”true”
E、注意
1)、二級快取是事務性的。當 SqlSession 完成commit時或是回滾,但未執行 flushCache=true 的 insert/delete/update 語句時,快取會獲得更新(但不能是同一個SqlSession的查詢commit);
2)、MyBatis在多表查詢時,極大可能會出現髒資料,設計上存在缺陷;
3)、在分散式環境下,由於預設的MyBatis Cache實現都是基於本地的,分散式環境下必然會出現讀取到髒資料,需要使用集中式快取將MyBatis的Cache介面實現,有一定的開發成本。使用Redis、Memcached等分佈
式快取可能成本更低,安全性也更高(後續博文釋出);
4)、觸發二級快取的時機為session.close時;
5)、XxxMapper.xml檔案中的實體類需要做序列化操作,級聯類及父類也需要序列化操作;
應用例項:
1、 實體類
public class Person implements Serializable { static final long serialVersionUID = 42L; /* 人員ID */ private int id; /* 人員名稱 */ private String name; /* 人員年齡 */ private int age; /* 人員性別 */ private Boolean sex; public Person() { } public Person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public Person(int id, String name, int age, Boolean sex) { this.id = id; this.name = name; this.age = age; this.sex = sex; } public Boolean getSex() { return sex; } public void setSex(Boolean sex) { this.sex = sex; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
2、 介面類
// 操作mybatis介面 public interface PersonMapper { Person queryPersonById(int id); }
3、 Mapper.xml檔案
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace:該mapper.xml對映檔案的唯一標識 --> <mapper namespace="com.cache.mapper.PersonMapper"> <!- 開啟二級快取--> <cache/> <select id="queryPersonById" parameterType="int" resultType="com.cache.bean.Person"> select id,name,age from t_person where id = #{id} </select> </mapper>
4、pom檔案
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 開啟日誌,並制定使用的具體日誌,LOG4J 對應 log4j.properties的檔名 --> <!--<setting name="logImpl" value="LOG4J"/>--> <!-- 開啟延遲載入 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 關閉立即載入 --> <setting name="aggressiveLazyLoading" value="false"/> <!-- 開啟二級快取,預設不開啟 --> <setting name="cacheEnabled" value="true"/> <!--<setting name="localCacheScope" value="STATEMENT"/>--> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis01"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/cache/mapper/personMapper.xml"/> </mappers> </configuration>
5、 測試類
/* 二級快取 */ @Test public void test001() throws Exception{ Reader reader = Resources.getResourceAsReader("mybatis-01.xml"); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession session01 = sessionFactory.openSession(); PersonMapper personMapper01 = session01.getMapper(PersonMapper.class); Person person0101 = personMapper01.queryPersonById(1001); System.out.println("Session01,第一次查詢結果為:" + person0101); session01.close(); // 進行快取操作 SqlSession session02 = sessionFactory.openSession(); PersonMapper personMapper02 = session02.getMapper(PersonMapper.class); Person person0201 = personMapper02.queryPersonById(1001); System.out.println("Session02,第一次查詢結果為:" + person0201); Person person0202 = personMapper02.queryPersonById(1001); System.out.println("Session02,第二次查詢結果為:" + person0202); Person person0203 = personMapper02.queryPersonById(1001); System.out.println("Session02,第三次查詢結果為:" + person0203); session02.close(); // 進行快取操作 }
6、測試結果
"C:\Program Files\Java\jdk1.8.0_25\bin\java" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\lib\idea_rt.jar=1045:C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\bin" -Dfile.encoding=UTF-8 -classpath "C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\lib\idea_rt.jar;C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\plugins\junit\lib\junit-rt.jar;C:\Users\newsoft\AppData\Roaming\JetBrains\IntelliJ IDEA 2017.3.5\plugins\junit\lib\junit5-rt.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\platform\junit-platform-launcher\1.5.2\junit-platform-launcher-1.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\apiguardian\apiguardian-api\1.1.0\apiguardian-api-1.1.0.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\platform\junit-platform-engine\1.5.2\junit-platform-engine-1.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\opentest4j\opentest4j\1.2.0\opentest4j-1.2.0.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\platform\junit-platform-commons\1.5.2\junit-platform-commons-1.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\jupiter\junit-jupiter-engine\5.5.2\junit-jupiter-engine-5.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\jupiter\junit-jupiter-api\5.5.2\junit-jupiter-api-5.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\junit\vintage\junit-vintage-engine\5.5.2\junit-vintage-engine-5.5.2.jar;D:\download\lib\mavenTollTransfer\mic-repository\junit\junit\4.12\junit-4.12.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\rt.jar;D:\ideaworkspace\ProjectStudy\mybatis-cache-03\target\classes;D:\download\lib\mavenTollTransfer\mic-repository\org\hamcrest\hamcrest-core\2.1\hamcrest-core-2.1.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\hamcrest\hamcrest\2.1\hamcrest-2.1.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\mybatis\mybatis\3.4.5\mybatis-3.4.5.jar;D:\download\lib\mavenTollTransfer\mic-repository\mysql\mysql-connector-java\5.1.44\mysql-connector-java-5.1.44.jar;D:\download\lib\mavenTollTransfer\mic-repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\slf4j\slf4j-log4j12\1.7.12\slf4j-log4j12-1.7.12.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\download\lib\mavenTollTransfer\mic-repository\cglib\cglib\3.3.0\cglib-3.3.0.jar;D:\download\lib\mavenTollTransfer\mic-repository\org\ow2\asm\asm\7.1\asm-7.1.jar;D:\download\lib\mavenTollTransfer\mic-repository\net\logstash\logback\logstash-logback-encoder\5.3\logstash-logback-encoder-5.3.jar;D:\download\lib\mavenTollTransfer\mic-repository\com\fasterxml\jackson\core\jackson-databind\2.10.3\jackson-databind-2.10.3.jar;D:\download\lib\mavenTollTransfer\mic-repository\com\fasterxml\jackson\core\jackson-annotations\2.10.3\jackson-annotations-2.10.3.jar;D:\download\lib\mavenTollTransfer\mic-repository\com\fasterxml\jackson\core\jackson-core\2.10.3\jackson-core-2.10.3.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit5 com.cache.test.test,test001 [lsjSso]2021-10-21 00:18:40,415-org.apache.ibatis.logging.LogFactory-0 [main]DEBUGorg.apache.ibatis.logging.LogFactory-Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. [lsjSso]2021-10-21 00:18:40,563-org.apache.ibatis.datasource.pooled.PooledDataSource-148 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-PooledDataSource forcefully closed/removed all connections. [lsjSso]2021-10-21 00:18:40,563-org.apache.ibatis.datasource.pooled.PooledDataSource-148 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-PooledDataSource forcefully closed/removed all connections. [lsjSso]2021-10-21 00:18:40,564-org.apache.ibatis.datasource.pooled.PooledDataSource-149 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-PooledDataSource forcefully closed/removed all connections. [lsjSso]2021-10-21 00:18:40,564-org.apache.ibatis.datasource.pooled.PooledDataSource-149 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-PooledDataSource forcefully closed/removed all connections. [lsjSso]2021-10-21 00:18:40,671-com.cache.mapper.PersonMapper-256 [main]DEBUGcom.cache.mapper.PersonMapper-Cache Hit Ratio [com.cache.mapper.PersonMapper]: 0.0 [lsjSso]2021-10-21 00:18:40,679-org.apache.ibatis.transaction.jdbc.JdbcTransaction-264 [main]DEBUGorg.apache.ibatis.transaction.jdbc.JdbcTransaction-Opening JDBC Connection [lsjSso]2021-10-21 00:18:40,938-org.apache.ibatis.datasource.pooled.PooledDataSource-523 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-Created connection 2009221452. [lsjSso]2021-10-21 00:18:40,938-org.apache.ibatis.transaction.jdbc.JdbcTransaction-523 [main]DEBUGorg.apache.ibatis.transaction.jdbc.JdbcTransaction-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77c2494c] [lsjSso]2021-10-21 00:18:40,942-com.cache.mapper.PersonMapper.queryPersonById-527 [main]DEBUGcom.cache.mapper.PersonMapper.queryPersonById-==> Preparing: select id,name,age from t_person where id = ? [lsjSso]2021-10-21 00:18:40,984-com.cache.mapper.PersonMapper.queryPersonById-569 [main]DEBUGcom.cache.mapper.PersonMapper.queryPersonById-==> Parameters: 1001(Integer) [lsjSso]2021-10-21 00:18:41,012-com.cache.mapper.PersonMapper.queryPersonById-597 [main]DEBUGcom.cache.mapper.PersonMapper.queryPersonById-<== Total: 1 Session01,第一次查詢結果為:Person{id=1001, name='HuanCun', age=26} [lsjSso]2021-10-21 00:18:41,017-org.apache.ibatis.transaction.jdbc.JdbcTransaction-602 [main]DEBUGorg.apache.ibatis.transaction.jdbc.JdbcTransaction-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77c2494c] [lsjSso]2021-10-21 00:18:41,017-org.apache.ibatis.transaction.jdbc.JdbcTransaction-602 [main]DEBUGorg.apache.ibatis.transaction.jdbc.JdbcTransaction-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@77c2494c] [lsjSso]2021-10-21 00:18:41,017-org.apache.ibatis.datasource.pooled.PooledDataSource-602 [main]DEBUGorg.apache.ibatis.datasource.pooled.PooledDataSource-Returned connection 2009221452 to pool. [lsjSso]2021-10-21 00:18:41,019-com.cache.mapper.PersonMapper-604 [main]DEBUGcom.cache.mapper.PersonMapper-Cache Hit Ratio [com.cache.mapper.PersonMapper]: 0.5 Session02,第一次查詢結果為:Person{id=1001, name='HuanCun', age=26} [lsjSso]2021-10-21 00:18:41,019-com.cache.mapper.PersonMapper-604 [main]DEBUGcom.cache.mapper.PersonMapper-Cache Hit Ratio [com.cache.mapper.PersonMapper]: 0.6666666666666666 Session02,第二次查詢結果為:Person{id=1001, name='HuanCun', age=26} [lsjSso]2021-10-21 00:18:41,020-com.cache.mapper.PersonMapper-605 [main]DEBUGcom.cache.mapper.PersonMapper-Cache Hit Ratio [com.cache.mapper.PersonMapper]: 0.75 Session02,第三次查詢結果為:Person{id=1001, name='HuanCun', age=26} Process finished with exit code 0
結果分析說明: