深度剖析MyBatis的一级缓存

概述

  • MyBatis 的一级缓存是什么时候开启的?

    • 在 MyBatis 中, 一级缓存是默认开启的

参考:MyBatis缓存的概念

通过场景来理解:

场景一

1、在一个 SqlSession 中,对 User 表进行两次根据 ID 的查询,查看发出 sql 语句的情况。

/**
 * 根据ID查询用户
 *
 * @param id
 * @return
 */
@Select("select * from user where id=#{id}")
User findUserById(Integer id);

@Before
public void before() throws Exception {
    System.out.println("before...");
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    //根据 sqlSessionFactory 产生 session
    sqlSession = sqlSessionFactory.openSession();
    // 这样也是可以的,这样的话后面就不用每次都设置了
    // sqlSession = sqlSessionFactory.openSession(true);
    userMapper = sqlSession.getMapper(IUserMapper.class);
}

@Test
public void testFindUserById() {
    //第一次查询,发出sql语句,并将查询出来的结果放进缓存中
    User user = userMapper.findUserById(1);
    System.out.println(user);

    //第二次查询,由于是同一个sqlSession,会在缓存中查询结果 //如果有,则直接从缓存中取出来,不和数据库进行交互
    User user2 = userMapper.findUserById(1);
    System.out.println(user2);
}

sql 执行过程如下:

Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1677d1]
==>  Preparing: select * from user where id=?
==> Parameters: 1(Integer)
<==    Columns: id, username, password, birthday
<==        Row: 1, lucy, 123, 2019-12-12
<==      Total: 1
User{id=1, username='lucy', orderList=null, roleList=null}
User{id=1, username='lucy', orderList=null, roleList=null}

Process finished with exit code 0

场景二

2、同样对 user 表进行两次查询,不同的是两次查询之间进行了一次 update 操作

@Test
public void testFindUserById2() {
    // 第一次查询
    User user = userMapper.findUserById(1);
    System.out.println(user);

    // 更新操作
    user.setUsername("tyw");
    userMapper.update(user);

    // 第二次查询
    User user2 = userMapper.findUserById(1);
    System.out.println(user2);
}

可以看到,第一次查询后,进行了更新,然后进行第二次查询,这里两次查询都输出了 sql,说明缓存没有生效。

Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@48fa0f47]
==>  Preparing: select * from user where id=?
==> Parameters: 1(Integer)
<==    Columns: id, username, password, birthday
<==        Row: 1, lucy, 123, 2019-12-12
<==      Total: 1
User{id=1, username='lucy', orderList=null, roleList=null}
==>  Preparing: update user set username=? where id=?
==> Parameters: tyw(String), 1(Integer)
<==    Updates: 1
==>  Preparing: select * from user where id=?
==> Parameters: 1(Integer)
<==    Columns: id, username, password, birthday
<==        Row: 1, tyw, 123, 2019-12-12
<==      Total: 1
User{id=1, username='tyw', orderList=null, roleList=null}

总结

1、第一次查询用户 ID 为 1 的用户信息,先去缓存查找有没有 ID 为 1 的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将数据保存到一级缓存中。

2、如果 SqlSession 执行了 commit 操作(执行插入、更新、删除),则会情况 SqlSession 的一级缓存。这样做的目的是保存一级缓存中额数据是最新数据,防止脏读。

3、第二次发起查询 ID 为 1 的用户信息,先去缓存中查询 ID 为 1 的用户信息,如果有,直接返回。

一级缓存查找过程

一级缓存原理探究与源码分析

问题抛出

一级缓存是什么?一级缓存什么时候被创建?一级缓存的工作流程是什么?

SqlSession 中与缓存相关的属性和方法

跟踪一下 clearCache 的子类和父类

@startuml !include https://unpkg.com/plantuml-style-c4@latest/core.puml ' uncomment the following line and comment the first to use locally '!include core.puml '!theme plain top to bottom direction skinparam linetype ortho class BaseExecutor class DefaultSqlSession class PerpetualCache interface SqlSession << interface >> BaseExecutor -[#595959,dashed]-> PerpetualCache : "«create»" BaseExecutor "1" *-[#595959,plain]-> "localCache\n1" PerpetualCache DefaultSqlSession -[#008200,dashed]-^ SqlSession @enduml

简单来看

可以看到,cache 的最底层其实就是一个 HashMap

public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  ...
}

缓存其实就是本地存放的一个 Map 对象,每一个 SqlSession 都会存放一个 map 对象的引用。

cache 的创建时机

Executor 是执行器,用来执行 SQL 请求,而且清除缓存的方法也在 Executor 中执行,所以很可能缓存的创建也很 有可能在 Executor 中。

Executor 中有一个 createCacheKey 方法,这个方法很像是创建缓存的方法,跟进去看看,发现 createCacheKey 方法是由 BaseExecutor 执行的,代码如下

@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
        if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);
        }
    }
    if (configuration.getEnvironment() != null) {
        // issue #176
        cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
}

创建缓存 key 会经过一系列的 update 方法,update 方法由一个 CacheKey 这个对象来执行的,这个 update 方法最终由 updateList 的 list 来把五个值存进去。

public void update(Object object) {
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

    count++;
    checksum += baseHashCode;
    baseHashCode *= count;

    hashcode = multiplier * hashcode + baseHashCode;

    updateList.add(object);
}

这里需要注意一下最后一个值,configuration.getEnvironmen().getId() 这是什么,这其实就是定义在 sqlMapConfig.xml 中的标签,⻅如下。

<!-- environments:运行环境 -->
<environments default="development">
    <environment id="development">
       ...
    </environment>
    <environment id="production">
       ...
    </environment>
</environments>

一级缓存的使用

一级缓存更多是用于查询操作,毕竟一级缓存也叫做查询缓存。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
	    // 处理存储过程
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    if (queryStack == 0) {
        for (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

queryFromDatabase 方法

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

如果查不到的话,就从数据库查,在 queryFromDatabase 中,会对 localcache 进行写入。 localcache 对象的 putObject 方法调用 PerpetualCache 类 的 put 方法,最终交给 Map 进行存放。

@Override
public void putObject(Object key, Object value) {
    cache.put(key, value);
}

文章更新历史

2024/05/15 同步文章到其他平台

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

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

相关文章

一步一步带你做网络工程

网络工程怎么做 一、网络设备交换机的应用&#xff1a; 要求&#xff1a;在此接入交换机S3700&#xff0c;上划分两个vlan&#xff0c;vlan10和vlan20分别有两个PC&#xff0c;按拓扑图完成要求&#xff1a; 划分vlan添加端口 sys [Huawei]sys S1 [S1]undo in e [S1]undo t…

2024年重庆等保测评公司有哪些?分别位于哪里?

2024年重庆等保测评公司有哪些&#xff1f;分别位于哪里&#xff1f; 【回答】&#xff1a;目前2024年重庆等保测评公司有四家&#xff0c;具体公司名称以及地址如下&#xff1a; 1、重庆信安网络安全等级测评有限公司&#xff0c;重庆市两江新区黄山大道中段55号附2号麒麟D座…

新手小白如何使用云平台复现论文代码——体验yolov8监控交通流

介绍&#xff1a;YOLOv8 是一种开源目标检测算法&#xff08;模型&#xff09;&#xff0c;是 YOLO(You Only Look Once) 系列算法的最新版本。它使用单次预测框架对图像中的对象进行定位和分类。这种方法可以检测多个对象&#xff0c;并且速度更快&#xff0c;准确率更高。 参…

JavaScript引入方式

JS引入方式 1 内部脚本方式引入2 外部脚本方式引入 1 内部脚本方式引入 说明 在页面中,通过一对script标签引入JS代码script代码放置位置具备一定的随意性,一般放在head标签中居多 代码 <!DOCTYPE html> <html lang"en"><head><meta charset…

照片误删如何恢复?这些方法帮你重拾回忆!

手机照片是我们记录美好时刻的重要工具。但有时我们会因为不小心或者错误操作而导致珍贵照片的丢失。那些与家人、朋友共度的美好时刻、旅途中的风景、重要的纪念日&#xff0c;一旦删除&#xff0c;就如同从记忆中抹去&#xff0c;令人惋惜不已。幸运的是&#xff0c;随着科技…

JavaScript函数声明

JS函数声明 JS中的方法,多称为函数,函数的声明语法和JAVA中有较大区别 语法1&#xff1a;function 函数名 (参数列表){函数体} 语法2&#xff1a;var 函数名 function (参数列表){函数体} 函数说明 函数没有权限控制符不用声明函数的返回值类型,需要返回在函数体中直接return即…

低空经济之无人机

朋友们&#xff0c;今天来聊聊个超酷的话题——低空经济之无人机&#xff01; 无人机不仅让天空变得触手可及&#xff0c;还带来了无尽的商业可能&#xff0c;简直就是新时代的“空中小助手”啊&#xff01; 说到无人机&#xff0c;你们是不是也和我一样&#xff0c;脑海里立马…

20240514基于深度学习的弹性超材料色散关系预测与结构逆设计

论文&#xff1a;Dispersion relation prediction and structure inverse design of elastic metamaterials via deep learning DOI&#xff1a;https://doi.org/10.1016/j.mtphys.2022.100616 1、摘要 精心设计的超材料结构给予前所未有的性能&#xff0c;保证了各种各样的具…

Base64编码

一、什么是BASE64编码 在了解BASE64编码之前&#xff0c;首先回顾一下ASCII码&#xff1a; ASCII码&#xff1a; ASCII&#xff08;American Standard Code for Information Interchang&#xff09;漂亮国信息交换标准代码。 ASCII 码使用7个二进制位来表示一个字符&#xf…

推荐系统学习笔记(一)

A/B测试 随机分桶--哈希函数随机分 如果某个实验组指标明显优于对照组&#xff0c;则值得推全 分层实验 问题&#xff1a;流量不够用怎么办&#xff1f; 同层互斥----避免一个用户被两个实验影响 不同层正交----每层独立随机分配用户 为什么不能全都用正交&#xff1f; 同…

1060 爱丁顿数(测试点5)

solution1&#xff08;测试点5不通过&#xff09; 所谓“E天骑行超过E公里”&#xff0c;注意没有要求是第E天 对于直接判断变成了第E天骑行距离超过E公里&#xff0c;曲解了题意 例如对于 3 1 2 3输出为1 第1天骑行3公里&#xff0c;满足条件&#xff1b;第2天骑行2公里&…

图像数据集可视化查看代码

1 数据集 数据集为10分类问题 2 数据集可视化代码 注意事项&#xff1a; cv2.imread()函数中的路径不能包括中文&#xff0c;否则无法正常读取。 import matplotlib.pyplot as plt import matplotlib.image as mpimg from mpl_toolkits.axes_grid1 import ImageGridimport…

八大设计模式:适配器模式实现栈的泛型编程 | 通用数据结构接口的秘诀(文末送书)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

如何在阿里云申请免费SSL证书(三个月有效)

SSL证书主要用于建立Web服务器和客户端间可信的HTTPS协议加密链接&#xff0c;以防止数据在传输过程中被篡改&#xff0c;避免信息泄露。阿里云提供了多种品牌和类型的SSL证书&#xff0c;以满足不同用户的需求。您可以根据自己的预算、域名类型以及网站类型&#xff0c;选择购…

蓝桥杯练习系统(算法训练)ALGO-941 P0601字符删除

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 编写一个程序&#xff0c;先输入一个字符串str&#xff08;长度不超过20&#xff09;&#xff0c;再输入单独的一个字符ch&#xff0c…

45.WEB渗透测试-信息收集-域名、指纹收集(7)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;计算机王-CSDN博客 WEB指纹&#xff1a;Web指纹也叫web应用指纹。由于所使用的工具、技术…

【全开源】国际版JAVA多商户运营版商城系统源码地摊兄源码多商户源码社交电商源码支持Android+IOS+H5

国际版多商户运营版商城系统&#xff1a;打造全球电商新生态 随着全球化趋势的深入发展&#xff0c;跨境电商已成为推动世界经济增长的重要力量。为了满足不同国家、地区商户的多样化需求&#xff0c;我们隆重推出“国际版多商户运营版商城系统”&#xff0c;旨在为全球商户搭…

智能监控与安全管理:安全帽检测算法的实践与应用

在工地、煤矿等高危工作环境中&#xff0c;安全帽的佩戴至关重要。安全帽能够有效防止因坠落物体或碰撞等引起的头部伤害&#xff0c;从而保护工作人员的生命安全。然而&#xff0c;传统的检查人员佩戴安全帽的方式主要依赖于现场监督和巡查&#xff0c;这种方法不仅耗费大量人…

Linux修炼之路之gcc/g++,动静态链接及动静态库

目录 一&#xff1a;Linux编译器-gcc/g 预处理-编译-汇编-链接 1.预处理 2.编译 (生成汇编) 3.汇编(生成机器可识别代码) 4.链接(生成可执行文件或库文件) 三:动静态链接和动静态库 动静态库 动静态链接 1.动态链接 2.静态链接 3.注意点 4.各自优缺点 5.ldd和fil…

杀杀杀杀杀杀杀杀杀

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。 …