Mybatis一级缓存

一级缓存简介

        在常见的应用系统中,数据库是比较珍贵的资源,很容易成为整个系统的瓶颈。在设计和护系统时,会进行多方面的权衡,并且利用多种优化手段,减少对数据库的直接访问。使用缓存是一种比较有效的优化手段,使用缓存可以减少应用系统与数据库的网络交互、减少数据库访问次数、降低数据库的负担、降低重复创建和销毁对象等一系列开销,从而提高整个系统的性能。从另一方面来看,当数据库意外宕机时,缓存中保存的数据可以继续支持应用程序中的部分展示的功能,提高系统的可用性。
         MyBatis 作为一个功能强大的ORM框架,也提供了缓存的功能,其缓存设计为两层结构,分别为一级缓存和二级缓存。一级缓存是会话级别的缓存,在MyBatis中每创建一个SqlSession对象,就表示开启一次数据库会话。在一次会话中,应用程序可能会在短时间内,例如一个事务内,反复执行完全相同的查询语句,如果不对数据进行缓存,那么每一次查询都会执行一次数据库查询操作,而多次完全相同的、时间间隔较短的查询语句得到的结果集极有可能完全相同,这也就造成了数据库资源的浪费。
MyBatis 中的 SqlSession是通过Executor对象完成数据库操作的,为了避免上述问题,在Executor对象中会建立一个简单的缓存,它会将每次查询的结果对象缓存起来。在执行查询操作时,会先查询一级缓存,如果其中存在完全一样的查询语句,则直接从一级缓存中取出相应的结果对象并返回给用户,这样不需要再访问数据库了,从而减小了数据库的压力。
        一级缓存的生命周期与SqlSession相同,其实也就与SqlSession中封装的 Executor 对象的生命周期相同。当调用Executor对象的close()方法时,该Executor对象对应的一级缓存就变得不可用。一级缓存中对象的存活时间受很多方面的影响,例如,在调用Executor.update()方法时,也会先清空一级缓存。一级缓存默认是开启的,一般情况下,不需要用户进行特殊配置。

一级缓存命中现象演示

创建配置文件mybatis-config.xml

<?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>

    <properties>
        <property name="driver" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
    </properties>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <environments default="default">
        <environment id="default">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/FirstCacheMapper.xml" />
    </mappers>

</configuration>

创建FirstCacheMapper接口

public interface FirstCacheMapper {

    List<EmployeeDO> listAllEmployeeByDeptId(Integer deptId);

    List<EmployeeDO> listAllEmployeeByDeptIdCopy(Integer deptId);

    int updateEmployeeAgeById(@Param("age") Integer age, @Param("id") Integer id);
}

创建FirstCacheMapper.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">
<mapper namespace="com.ys.mybatis.mapper.FirstCacheMapper">

    <select id="listAllEmployeeByDeptId" resultType="com.ys.mybatis.DO.EmployeeDO">
        select * from employee where dept_id = #{deptId}
    </select>

    <select id="listAllEmployeeByDeptIdCopy" resultType="com.ys.mybatis.DO.EmployeeDO" >
        select * from employee where dept_id = #{deptId}
    </select>

    <update id="updateEmployeeAgeById">
        update employee set age = #{age} where id = #{id}
    </update>

</mapper>

创建测试类FirstCacheTest

@Slf4j
public class FirstCacheTest {
    private SqlSessionFactory sqlSessionFactory;

    private Configuration configuration;

    @BeforeEach
    public void before() {
        InputStream inputStream = ConfigurationTest.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        configuration = sqlSessionFactory.getConfiguration();
    }

    @Test
    public void hitFirstLevelCacheTest() {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        List<Object> firstQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);

        List<Object> secondQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);

        System.out.println(firstQuery == secondQuery);
    }
}

运行测试方法hitFirstLevelCacheTest

现象 : SQL只编译一次且只执行一次,两次查询的结果相等

源码简析

通过源码我们得出结论 : 每次查询会生成一个cacheKey,当我们的查询未命中缓存则查询数据库,否则直接返回缓存的内容

cacheKey的组成

cacheKey的组成

  • statementId
  • rowBounds
  • sql
  • 参数
  • environment : 主要针对二级缓存,一级缓存是session级别的缓存,当environment不同,则sqlSession肯定不是同一对象。对于二级缓存来说如果environment不同,即使sql 、参数、rowBounds等条件一致,也不会命中缓存

演示cacheKey不同的几种情况

@Test
public void differentStatementId() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> firstQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    List<Object> secondQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptIdCopy", 1, RowBounds.DEFAULT);
    System.out.println(firstQuery == secondQuery);
}

@Test
public void differentRowBounds() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> firstQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    List<Object> secondQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, new RowBounds(0, 10));
    System.out.println(firstQuery == secondQuery);
}

@Test
public void differentParameters() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> firstQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    List<Object> secondQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 2, RowBounds.DEFAULT);
    System.out.println(firstQuery == secondQuery);
}

一级缓存失效场景

除了因为cacheKey导致的缓存未命中,其他原因也有可能导致一级缓存未命中

1.手动清空缓存

@Test
public void manualClearing() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> firstQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    // 手动清空
    sqlSession.clearCache();
    List<Object> secondQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    System.out.println(firstQuery == secondQuery);
}

2.flushCache = true

@Test
public void flushCache() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> firstQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    List<Object> secondQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    System.out.println(firstQuery == secondQuery);
}

相关源码BaseExecutor#query

3.两次查询之间存在更新操作

@Test
public void updateInfoBetweenTwoQueries() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> firstQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);

    // 更新信息
    FirstCacheMapper mapper = sqlSession.getMapper(FirstCacheMapper.class);
    mapper.updateEmployeeAgeById(20, 2);

    List<Object> secondQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    System.out.println(firstQuery == secondQuery);
}

 相关源码BaseExecutor#update

4.作用域为STATEMENT

<settings>
    <setting name="localCacheScope" value="STATEMENT"/>
</settings>
@Test
public void statementScope() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<Object> firstQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    List<Object> secondQuery = sqlSession.selectList("com.ys.mybatis.mapper.FirstCacheMapper.listAllEmployeeByDeptId", 1, RowBounds.DEFAULT);
    System.out.println(firstQuery == secondQuery);
}

相关源码BaseExecutor#query

解决循环依赖

mybatis一级缓存不仅能减轻数据库的压力,还可以解决循环依赖

比如说现在有这样一个场景 : 博客里面有评论信息,评论里面有博客信息

@Data
public class Blog {

    private Integer id;

    private String title;

    private List<Comment> comments;
}
@Data
public class Comment {

    private Integer blogId;

    private String content;

    private Blog blog;
}
<resultMap id="blogMap" type="com.ys.mybatis.DO.Blog">
    <id column="id" property="id"/>
    <result column="title" property="title"/>
    <collection property="comments" column="id" select="getCommentByBlogId"/>
</resultMap>

<resultMap id="commentMap" type="com.ys.mybatis.DO.Comment">
    <result property="blogId" column="blog_id"/>
    <result property="content" column="content"/>
    <association property="blog" column="blog_id" select="getBlogInfoById"/>
</resultMap>

<select id="getBlogInfoById" resultMap="blogMap">
    select * from blog where id = #{id}
</select>

<select id="getCommentByBlogId" resultMap="commentMap">
    select * from comment where blog_id = #{blogId}
</select>

上述情景,会出现循环依赖,那么mybatis是如何解决循环依赖的,我们查看相关源码

BaseExecutor#query

BaseExecutor#queryFromDatabase

DefaultResultSetHandler#getNestedQueryMappingValue

相关源码比较多,还有很多流程是重复了,这里就标注了比较重要的在步骤。整体流程,详见下方流程图

mybatis利用queryStack、一级缓存、延迟加载完成了循环依赖 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/526161.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

干货分享 | 在TSMaster中加载基于DotNet平台的SeedKey

在UDS诊断过程中&#xff0c;会涉及到安全访问的问题&#xff0c;也就是所谓的Seed&Key。TSMaster 诊断模块支持通过.dll文件载入 Seed&Key 算法用于安全访问解锁。在最近发布的TSMaster 2024.03版本中不仅支持了C/C&#xff0c;Delphi等语言封装的DLL文件&#xff0c;…

【CVE复现计划】CVE-2024-0195

CVE-2024-0195 简介&#xff1a; SpiderFlow是新一代开源爬虫平台&#xff0c;以图形化方式定义爬虫流程&#xff0c;不写代码即可完成爬虫。基于springbootlayui开发的前后端不分离,也可以进行二次开发。该系统/function/save接口存在RCE漏洞&#xff0c;攻击者可以构造恶意命…

蓝帕控制阀门将莅临2024年第13届生物发酵展

参展企业介绍 感谢你正在或即将使用蓝帕控制阀门(江苏)有限公司系列产品&#xff0c;感谢你关注蓝帕控制阀门(江苏)有限公司&#xff01; 蓝帕控制阀门(江苏)有限公司&#xff0c;流体控制领域的国际品牌之一&#xff0c;总部位于意大利米兰&#xff0c;成立多年以来&#xf…

流式密集视频字幕

流式密集视频字幕 摘要1 IntroductionRelated Work3 Streaming Dense Video Captioning Streaming Dense Video Captioning 摘要 对于一个密集视频字幕生成模型&#xff0c;预测在视频中时间上定位的字幕&#xff0c;理想情况下应该能够处理长的输入视频&#xff0c;预测丰富、…

Rust 标准库 API 文件和文件夹操作 File,读取/创建/修改/追加/删除/重命名文件等

File::create 使用File的关联函数&#xff08;类似Java中的静态方法&#xff09;create&#xff0c;创建文件&#xff0c;如果存在&#xff0c;则覆盖。 use std::fs::{File, Metadata};fn main() -> std::io::Result<()> {let file: File File::create("foo.…

C++ 学习笔记

文章目录 【 字符串相关 】C 输入输出流strcpy_s() 字符串复制输出乱码 【 STL 】各个 STL 支持的常见方法 ? : 运算符switch case 运算符 switch(expression) {case constant-expression :statement(s);break; // 可选的case constant-expression :statement(s);break; //…

基于arcgis /envi PCA(主成分分析)实现过程

基于arcgis /envi PCA(主成分分析)实现过程 1 提取研究范围 2对研究范围进行重采样 &#xff08;根据数据情况进行选做&#xff0c;如数据较大建议进行该步骤操作&#xff09; 3 对研究范围内数据进行归一化处理 4 将空值替换为0 5 对同期不同要素数据进行波段合成 对波段…

如何本地部署JumpServer堡垒机并结合内网穿透实现远程访问

文章目录 前言1. 安装Jump server2. 本地访问jump server3. 安装 cpolar内网穿透软件4. 配置Jump server公网访问地址5. 公网远程访问Jump server6. 固定Jump server公网地址 前言 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。JumpS…

云南省气象探空业务升级为北斗探空观测系统

云南省气象探空业务升级为北斗探空观测系统 近日&#xff0c;云南省首套北斗探空观测系统在普洱市思茅区高空气象观测站建成并调试成功&#xff0c;这意味着云南省气象探空业务将从L波段雷达探测升级到北斗探空观测系统。 &#xff08;图片来源于网络&#xff09; 北斗探空观…

距离度量方法——欧氏距离、曼哈顿距离、切比雪夫距离、闵可夫斯基距离

目录 一、 欧氏距离&#xff08;Euclidean Distance&#xff09; 1、简介 2、代码实现 二、曼哈顿距离&#xff08;Manhattan Distance&#xff09; 1、简介 2、代码实现 三、切比雪夫距离&#xff08;Chebyshev Distance&#xff09; 1、简介 2、代码实现 四、闵可夫…

CSS实现热门创作者排行榜(毛玻璃效果)

CSS实现热门创作者排行榜&#xff08;毛玻璃效果&#xff09; 效果展示 CSS 知识点 CSS 基础知识回顾filter 属性运用回顾 整体页面布局实现 <div class"container"><h3>Popular Creator Rank List</h3><!-- 用户列表容器 --><div cl…

基于SpringBoot+Vue的健身教练预约管理系统(源码+文档+部署+讲解)

一.系统概述 私人健身与教练预约管理系统&#xff0c;可以摆脱传统手写记录的管理模式。利用计算机系统&#xff0c;进行用户信息、管理员信息的管理&#xff0c;其中包含首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;教练管理&#xff0c;健身项目管理&#xff0…

【个人使用推荐】联机不卡顿 小白一键部署 大厂云服务器选购指南 16G低至26 幻兽帕鲁最大更新来袭

更新日期&#xff1a;4月8日&#xff08;半年档 价格回调&#xff0c;京东云采购季持续进行&#xff09; 本文纯原创&#xff0c;侵权必究 《最新对比表》已更新在文章头部—腾讯云文档&#xff0c;文章具有时效性&#xff0c;请以腾讯文档为准&#xff01; 【腾讯文档实时更…

两款工业摄像头EPICS环境使用测试

从模拟摄像头进步到GIGE摄像头使得束斑监测系统的搭建方便多了&#xff0c;EPICS areaDetector下最开始使用的是进口的AVT的摄像头&#xff0c;后来发现海康摄像头便宜又好用&#xff0c;后来就一直使用海康的&#xff0c;MV-CA016-10GM这款在EPICS下使用一直很稳定&#xff0c…

ctfshow web入门 php特性 web140--web150plus

web140 这里用松散比较的漏洞绕过 0和字符串比较的时候就是true $code eval("return $f1($f2());"); 等于0就可以传参 POST: f1intval&f2intval查看源码 web141 if(preg_match(/^\W$/, $v3)) 是一段 PHP 代码&#xff0c;它使用了正则表达式函数 preg_mat…

【复现】畅捷通T-Plus SQL注入漏洞_68

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 畅捷T是用友畅捷通推出的一款新型互联网企业管理系统&#xff0c;T能够满足成长型小微企业对其灵活业务流程的管控需求&#xff0…

socket之UDP组播(多播)

组播也可以称之为多播这也是 UDP 的特性之一。组播是主机间一对多的通讯模式&#xff0c;是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将一份报文发送到特定的组播地址&#xff0c;组播地址不同于单播地址&#xff0c;它并不属于特定某个主机&#xff0c…

Enzo Life Sciences--17β-Estradiol high sensitivity ELISA kit

高灵敏ELISA试剂盒&#xff0c;可检测到低至14 pg/ml的17β-雌二醇 雌二醇(estradiol) 是由卵巢内卵泡的颗粒细胞分泌的类固醇激素&#xff0c;是主要的雌激素&#xff0c;负责调节女性特征、附属性器官的成熟和月经-排卵周期&#xff0c;促进乳腺导管系统的产生&#xff0c;有…

鸿蒙南向开发:【智能烟感】

样例简介 智能烟感系统通过实时监测环境中烟雾浓度&#xff0c;当烟雾浓度超标时&#xff0c;及时向用户发出警报。在连接网络后&#xff0c;配合数字管家应用&#xff0c;用户可以远程配置智能烟感系统的报警阈值&#xff0c;远程接收智能烟感系统报警信息。实现对危险及时报…

【大功率汽车大灯升压方案】LED恒流驱动芯片FP7208升压车灯调光应用,PWM内部转模拟,调光深度1%,无频闪顾虑,低亮无抖动

宝马X5前中排座椅宽大舒适&#xff0c;车厢内储物空间丰富。操控性能极佳&#xff0c;底盘稳扎精良。原车为氙气灯&#xff0c;其实宝马的氙气大灯配的比其他车型要好&#xff0c;照明效果是没得说的。但是不管什么灯久了都会出现光衰的情况。下面这辆宝马X5车灯已老化严重。 宝…