????????做實驗報告的時候,跟著學習,發現我已經將 開啟 二級緩存的 配置都配置好了,但是返回值地址不一致,說明對象不一致,二級緩存命中失敗。
跟著流程配置:
mybatis-config
<settings><!-- 啟用 mybatis 全局緩存 --><setting name="cacheEnabled" value="true"/><setting name="logImpl" value="LOG4J"/>
</settings>
Mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.Angindem.mapper.DynamicMapper"><cache/><select id="queryByEntities" resultType="com.Angindem.Entity.EmployeesEntity" useCache="true">select * from employees<where><foreach collection="emp" index="fld" item="val" separator="and">${fld} = #{val}</foreach></where></select></mapper>
TestCode:
@Testpublic void testGlobalCache() throws IOException {// 加載配置文件InputStream is = Resources.getResourceAsStream("mybatis-config.xml");// 創建 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);// 創建 sqlsession(得到 sql 語句,并執行)SqlSession session1 = factory.openSession(true); // 如果參數為空 默認 false 手動提交事務// 獲取 mapper 對象(代理模式,可以囊組返回當前接口的實現類對象)DynamicMapper dynamicMapper1 = session1.getMapper(DynamicMapper.class);SqlSession session2 = factory.openSession(true); // 如果參數為空 默認 false 手動提交事務DynamicMapper dynamicMapper2 = session2.getMapper(DynamicMapper.class);Map<String, Object> emp = new HashMap<>();emp.put("OfficeCode", 1);// 調用 SqlSession 執行一次 Mybatis,保存到本地緩存(二級級緩存)List<EmployeesEntity> list = dynamicMapper1.queryByEntities(emp);// 重復 操作 list2 接受 該語句List<EmployeesEntity> list2 = dynamicMapper2.queryByEntities(emp);// 判斷是否命中本地緩存,如果命中了,則返回的地址是相同的System.out.println("所接受的兩個鏈表地址一致? 結果:" + (list == list2));System.out.println("===================================\n\n");list.stream().forEach(s -> System.out.println(s));}
最后運行效果:
上網查資料,說 需要將運行的SqlSession會話關閉:
??????? 根據我的理解,需要將 SqlSession 關閉,本地緩存 與 SqlSession 的綁定,解綁后,將本地緩存,傳到 二級緩存那里才可以。
修改后的 TestCode:
public void testGlobalCache() throws IOException {// 加載配置文件InputStream is = Resources.getResourceAsStream("mybatis-config.xml");// 創建 SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);// 創建 sqlsession(得到 sql 語句,并執行)SqlSession session1 = factory.openSession(true); // 如果參數為空 默認 false 手動提交事務// 獲取 mapper 對象(代理模式,可以囊組返回當前接口的實現類對象)DynamicMapper dynamicMapper1 = session1.getMapper(DynamicMapper.class);SqlSession session2 = factory.openSession(true); // 如果參數為空 默認 false 手動提交事務DynamicMapper dynamicMapper2 = session2.getMapper(DynamicMapper.class);Map<String, Object> emp = new HashMap<>();emp.put("OfficeCode", 1);// 調用 SqlSession 執行一次 Mybatis,保存到本地緩存(一級緩存)List<EmployeesEntity> list = dynamicMapper1.queryByEntities(emp);// 關閉當前 SqlSession ,解綁 一級緩存,將語句 放到二級緩存session1.close();// 重復 操作 list2 接受 該語句List<EmployeesEntity> list2 = dynamicMapper2.queryByEntities(emp);// 關閉當前 SqlSession ,解綁 一級緩存,將語句 放到二級緩存session2.close();// 判斷是否命中本地緩存,如果命中了,則返回的地址是相同的System.out.println("所接受的兩個鏈表地址一致? 結果:" + (list == list2));System.out.println("===================================\n\n");list.stream().forEach(s -> System.out.println(s));}
最后運行效果:
報錯了,信息如下:
org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: com.Angindem.Entity.EmployeesEntityat org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:95)at org.apache.ibatis.cache.decorators.SerializedCache.putObject(SerializedCache.java:56)at org.apache.ibatis.cache.decorators.LoggingCache.putObject(LoggingCache.java:49)at org.apache.ibatis.cache.decorators.SynchronizedCache.putObject(SynchronizedCache.java:43)at org.apache.ibatis.cache.decorators.TransactionalCache.flushPendingEntries(TransactionalCache.java:116)at org.apache.ibatis.cache.decorators.TransactionalCache.commit(TransactionalCache.java:99)at org.apache.ibatis.cache.TransactionalCacheManager.commit(TransactionalCacheManager.java:45)at org.apache.ibatis.executor.CachingExecutor.close(CachingExecutor.java:61)at org.apache.ibatis.session.defaults.DefaultSqlSession.close(DefaultSqlSession.java:260)at com.Angindem.test.TestOffice.testGlobalCache(TestOffice.java:241)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.base/java.lang.reflect.Method.invoke(Method.java:568)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)at org.junit.runners.ParentRunner.run(ParentRunner.java:413)at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:93)at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:757)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: java.io.NotSerializableException: com.Angindem.Entity.EmployeesEntityat java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1197)at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:354)at java.base/java.util.ArrayList.writeObject(ArrayList.java:866)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.base/java.lang.reflect.Method.invoke(Method.java:568)at java.base/java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1074)at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1526)at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1448)at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1191)at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:354)at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:91)... 35 more
拋出了關鍵的異常:?? java.io.NotSerializableException
繼續查資料,解釋了一下這個異常:
java.io.NotSerializableException 是一個在 Java 程序中拋出的異常,屬于 java.io 包。這個異常表明嘗試序列化一個對象時,該對象的類沒有實現 java.io.Serializable 接口。序列化是 Java 中的一個機制,允許將對象的狀態保存到文件或通過網絡發送,以便之后可以重新創建該對象。 |
根據我自己的理解,Mybatis 中的二級緩存,就是需要將我們執行后的語句的本地緩存,保存并且上傳到 mybatis 的二級緩存機制中,所以需要將 對應的 Entity 對象 進行序列化,所以要在相應的類,加個接口,Serializable
修改我對應的類代碼:
package com.Angindem.Entity;import java.io.Serializable;public class EmployeesEntity implements Serializable {private Integer employeeNumber;private String lastName;private String firstName;private String extension;private String email;private Integer officeCode;private String jobTitle;public EmployeesEntity(Integer employeeNumber, String lastName, String firstName, String jobTitle) {super();this.employeeNumber = employeeNumber;this.lastName = lastName;this.firstName = firstName;this.jobTitle = jobTitle;}public Integer getEmployeeNumber() {return this.employeeNumber;}public void setEmployeeNumber(Integer employeeNumber) {this.employeeNumber = employeeNumber;}public String getlastName() {return lastName;}public void setlastName(String lastName) {this.lastName = lastName;}public String getFirstName() {return firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}public String getExtension() {return extension;}public void setExtension(String extension) {this.extension = extension;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public Integer getOfficeCode() {return officeCode;}public void setOfficeCode(Integer officeCode) {this.officeCode = officeCode;}public String getJobTitle() {return jobTitle;}public void setJobTitle(String jobTitle) {this.jobTitle = jobTitle;}@Overridepublic String toString() {return "Employees [employeeNumber=" + employeeNumber + ", lastName=" + lastName + ", firstName=" + firstName+ ", extension=" + extension + ", email=" + email + ", officeCode=" + officeCode + ", jobTitle="+ jobTitle + "]";}
}
最后運行效果:
運行成功了,發現還是沒有命中二級緩存
繼續查資料,原來標簽<cache/>有問題
修改后的 Mapper:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.Angindem.mapper.DynamicMapper"><cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/><select id="queryByEntities" resultType="com.Angindem.Entity.EmployeesEntity" useCache="true">select * from employees<where><foreach collection="emp" index="fld" item="val" separator="and">${fld} = #{val}</foreach></where></select>
</mapper>
這里解釋一下:
詳細配置的 <cache>
標簽:
????????MyBatis 的 <cache>
標簽用于配置 Mapper 級別的緩存行為。以下是兩個 <cache>
標簽配置的區別:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
-
eviction="FIFO"
: 指定了緩存的逐出策略為先進先出(FIFO)。這是當緩存達到其最大容量時用來決定哪些對象應該被移除的算法。 -
flushInterval="60000"
: 指定了緩存刷新的時間間隔,單位為毫秒。這里設置為 60000 毫秒,即每 60 秒緩存會被清空一次。 -
size="512"
: 指定了緩存中可以存儲的對象數量上限。這里設置為最多 512 個對象。 -
readOnly="true"
: 指定了緩存中的對象是只讀的。這通常可以提高性能,因為 MyBatis 不需要在每次查詢后都同步數據。
默認配置的 <cache>
標簽:
<cache/>
當 <cache>
標簽沒有包含任何屬性時,MyBatis 將使用默認的緩存配置。默認配置通常包括:
-
使用 LRU(最近最少使用)逐出策略。
-
沒有設置緩存刷新的時間間隔,緩存會在每次會話結束時清空。
-
默認的緩存大小沒有明確限制,但實際大小可能會受到 JVM 內存限制。
-
緩存中的對象不是只讀的,這意味著它們可以被修改。
總結來說,第一個 <cache>
標簽提供了詳細的緩存行為配置,包括逐出策略、刷新間隔、緩存大小和只讀屬性。而第二個 <cache>
標簽則使用 MyBatis 的默認緩存配置,沒有顯式設置這些屬性。使用詳細的配置可以幫助開發者根據應用的具體需求來優化緩存性能。
我的理解是,如果使用默認的<cache>
標簽,我們關閉了SqlSession會話后,其中的二級緩存也會在 每次的 會話結束時清空,所以我們沒有命中到前一個緩存。