当前位置: 首页 > news >正文

关于 Mybatis 的开启二级缓存返回对象不一致问题

        做实验报告的时候,跟着学习,发现我已经将 开启 二级缓存的 配置都配置好了,但是返回值地址不一致,说明对象不一致,二级缓存命中失败。

跟着流程配置:

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.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会话后,其中的二级缓存也会在 每次的 会话结束时清空,所以我们没有命中到前一个缓存。

最后成功运行效果:

相关文章:

  • 嵌入式PCB制图面试题及参考答案(2万字长文)
  • 【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化及多领域案例应用
  • 【2024德国签证】去德国读博士需要申请什么签证?
  • Spire.PDF for .NET【文档操作】演示:以特定的缩放比例/百分比打开 PDF 文件
  • 力扣习题--哈沙德数
  • Redis Stream Redisson Stream
  • Cube-Studio:开源大模型全链路一站式中台
  • 千益畅行,旅游卡,如何赚钱?
  • 【区块链+基础设施】国家健康医疗大数据科创平台 | FISCO BCOS应用案例
  • AMSA-UNet | 基于自注意力的多尺度 U-Net 提升图像去模糊性能
  • 「ETL趋势」FDL数据开发支持版本管理、实时管道支持多对一、数据源新增支持神通
  • element-plus Transfer 穿梭框半成品
  • 10年铲屎官亲自体验后,告诉你好用的空气净化器排名
  • Kotlin基础——异步和并发
  • Hadoop集群误删数据紧急恢复详细步骤
  • 【Leetcode】101. 对称二叉树
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • [译]Python中的类属性与实例属性的区别
  • Angularjs之国际化
  • centos安装java运行环境jdk+tomcat
  • Java超时控制的实现
  • MySQL几个简单SQL的优化
  • node 版本过低
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • Promise面试题2实现异步串行执行
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • 读懂package.json -- 依赖管理
  • 机器学习学习笔记一
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 三分钟教你同步 Visual Studio Code 设置
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • 树莓派 - 使用须知
  • 一文看透浏览器架构
  • 用mpvue开发微信小程序
  • puppet连载22:define用法
  • Semaphore
  • #ubuntu# #git# repository git config --global --add safe.directory
  • #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (03)光刻——半导体电路的绘制
  • (145)光线追踪距离场柔和阴影
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (3)STL算法之搜索
  • (4)事件处理——(7)简单事件(Simple events)
  • (二)换源+apt-get基础配置+搜狗拼音
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (附源码)计算机毕业设计SSM基于java的云顶博客系统
  • (生成器)yield与(迭代器)generator
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .NET HttpWebRequest、WebClient、HttpClient
  • .Net Memory Profiler的使用举例
  • .Net MVC4 上传大文件,并保存表单
  • .NET 表达式计算:Expression Evaluator