mybatis的一级缓存使用以及禁用

目录

验证代码如下

[mappper 代码](#mappper 代码)

[xml 中代码](#xml 中代码)

实际执行代码

执行结果

DefaultSqlSession

CachingExecutor

BaseExecutor

PerpetualCache

总结

禁用一级缓存

[mapper 对应的 xml 的 select 查询设置 flushCache 属性为 true](#mapper 对应的 xml 的 select 查询设置 flushCache 属性为 true)

[MappedStatement 的内部类 Builder 向外部变量 flushCacheRequired 赋值](#MappedStatement 的内部类 Builder 向外部变量 flushCacheRequired 赋值)

[MapperBuilderAssistant 的 setStatementCache()](#MapperBuilderAssistant 的 setStatementCache())

[XMLStatementBuilder 的 parseStatementNode() 解析 mapper 中定义的 sql](#XMLStatementBuilder 的 parseStatementNode() 解析 mapper 中定义的 sql)

[mybatis 全局配置 settings 中添加 name 为 localCacheScope 的节点,对应 value 为 STATEMENT](#mybatis 全局配置 settings 中添加 name 为 localCacheScope 的节点,对应 value 为 STATEMENT)

XMLConfigBuilder的settingsElement()方法

[有一个问题,为什么每次创建 SqlSession 缓存就不能共用了?](#有一个问题,为什么每次创建 SqlSession 缓存就不能共用了?)


在之前的文章基础上

https://blog.csdn.net/zlpzlpzyd/article/details/135171524

验证代码如下

mappper 代码

java 复制代码
package cn.hahaou.mybatis.cache.levelone.mapper;

import cn.hahaou.mybatis.cache.levelone.entity.Role;

public interface LevelOneRoleMapper {

    Role getRole(Long id);
}

xml 中代码

XML 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.hahaou.mybatis.cache.levelone.mapper.LevelOneRoleMapper">

    <resultMap id="roleMap" type="role">
        <id column="id" property="id" javaType="long" jdbcType="BIGINT"></id>
        <result column="role_name" property="roleName" javaType="string" jdbcType="VARCHAR"></result>
        <result column="note" property="note"></result>
    </resultMap>

    <select id="getRole" parameterType="long" resultType="role">
        select * from t_role t where t.id = #{id}
    </select>
</mapper>

实际执行代码

java 复制代码
package cn.hahaou.mybatis.cache.levelone;

import cn.hahaou.mybatis.cache.levelone.mapper.LevelOneRoleMapper;
import cn.hahaou.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;

/**
 * 一级缓存测试
 */
public class LevelOneCacheTest {

    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.openSession();
        LevelOneRoleMapper roleMapper = sqlSession.getMapper(LevelOneRoleMapper.class);
        roleMapper.getRole(1L);
        System.out.println("使用同一个SqlSession再执行一次");
        roleMapper.getRole(1L);
        sqlSession.close();
    }
}

执行结果

XML 复制代码
2023-12-23 19:30:56,109 [main] DEBUG org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
2023-12-23 19:30:56,204 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
2023-12-23 19:30:56,206 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
2023-12-23 19:30:56,206 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
2023-12-23 19:30:56,206 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
2023-12-23 19:31:15,482 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
2023-12-23 19:31:15,943 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 439928219.
2023-12-23 19:31:15,943 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1a38c59b]
2023-12-23 19:31:15,953 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select * from t_role t where t.id = ? 
2023-12-23 19:31:16,082 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Long)
2023-12-23 19:31:16,187 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
使用同一个SqlSession再执行一次
2023-12-23 19:32:41,371 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1a38c59b]
2023-12-23 19:32:41,378 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1a38c59b]
2023-12-23 19:32:41,379 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 439928219 to pool.

可以看到,最终查询只执行了一次。

在之前的获取 SqlSession 的基础上,有一个地方

这里的 Executor 是在 Configuration 中获取的,这里需要一个参数 ExecutorType,看看调用这里的地方是怎么传参的。

可知,默认是取的 Configuration 的 defaultExecutorType 的值,再到 Configuration 看一下。

由源码得知,Configuration 中 ExecutorType 的值默认为 SIMPLE。

回到刚才创建的地方,最终返回的结果为 CachingExecutor,通过装饰器模式的方式包装了 SimpleExecutor。

Executor的实现类

DefaultSqlSession

调用了 mapper 中的方法后,触发反射操作进入 MapperProxy 的 invoke()

鉴于当前查询是单值操作,走查询单条数据逻辑

MapperMethod 中有两个类型的变量 SqlCommand 和 MethodSignature。

SqlCommand 里面有两个变量

name 保存了当前访问的 mapper 方法。

type 对应 mapper 里的 sql 标签定义的各种 sql 操作类型,即 dml 操作

MethodSignature

对应 mapper 里的 sql 标签里的各个属性设置

通过 Configuration 获取 MappedStatement 对象。

MappedStatement 中 sqlSource 的类型是 RawSqlSource,通过责任链模式的方式内嵌了 StaticSqlSource,最终的 sql 在变量 sql 里。

调用 CachingExecutor 的 query()

CachingExecutor

在这里有两件事,创建 CacheKey 和调用嵌套的 SimpleExecutor 执行查询。其中 CacheKey 用于接下来缓存查询结果使用。

BaseExecutor

SimpleExecutor 是 BaseExecutor 的子类。CachingExecutor 中调用了 createCacheKey() 实际上调用了 CacheKey 的 createCacheKey()。

其中主要通过 update() 向里面的集合变量添加数据。具体信息如下

第1个为 MappedStatement 的 id,即需要查询的方法全路径

第2个为 RowBounds 的 offset,默认值为 0

第3个为 RowBounds 的 limit,默认值为 Integer.MAX_VALUE

第4个为 BoundSql 的 sql

第5个为查询的参数值

第6个为 Environment 信息,在配置中指定

定义了一个全局变量 localCache 缓存查询结果

首次查询没有数据,调用 queryFromDatabase() 从数据库中查询。否则,直接从缓存中获取数据。

查询结束后将 CacheKey 和对应的结果分为作为键值对保存到 localCache 中。

PerpetualCache

数据最终存储到了 map 对象中。

id 对应的是字符串 LocalCache

map 变量 cache 中,key 对应的是 CacheKey,value 对应的是返回的查询结果。

总结

可以看到,通过一个简单的查询,mybatis 使用了装饰器模式实现了一级缓存,默认启用,通过内存缓存当前查询结果,但是这个只适用于那种单体应用。

项目使用了集群部署的话使用一级缓存不太好,有缓存不一致的问题。如果数据量大的话会造成内存溢出的情况发生。

所以,针对项目部署的是集群环境,不要用一级缓存。如果是单体数据量不大可以使用。

鉴于一级缓存的执行逻辑在 BaseExecutor,所以二级缓存的全局启用设置 cacheEnabled 是否为 true 对于一级缓存没作用。

禁用一级缓存

可知,有两种方式

mapper 对应的 xml 的 select 查询设置 flushCache 属性为 true

XML 复制代码
<select flushCache="true" id="getRole" parameterType="long" resultType="role">
	select * from t_role t where t.id = #{id}
</select>

MappedStatement 的内部类 Builder 向外部变量 flushCacheRequired 赋值

MapperBuilderAssistant 的 setStatementCache()

setStatementCache() 的上层调用方法 addMappedStatement()

XMLStatementBuilder 的 parseStatementNode() 解析 mapper 中定义的 sql

可以看到,如果 SqlCommandType 值为 SELECT,flushCache 的值的情况如下

如果 flushCache 的值未设置,flushCache 值为 false,默认使用缓存。

如果 flushCache 的值设置为 true,flushCache 值为 true,禁止使用缓存。

mybatis 全局配置 settings 中添加 name 为 localCacheScope 的节点,对应 value 为 STATEMENT

XML 复制代码
<setting name="localCacheScope" value="STATEMENT"/>

其中 localCacheScope 实际对应的是枚举类型 LocalCacheScope,只有两个值,默认值为 SESSION。

XMLConfigBuilder的settingsElement()方法

java 复制代码
package org.apache.ibatis.session;

/**
 * @author Eduardo Macarron
 */
public enum LocalCacheScope {
  SESSION,STATEMENT
}

如果指定了其他值,创建 SqlSessionFactory 的过程中会出现异常。

这两种方式,要讲哪种方式好,还是全局方式好,当然,对于一级缓存禁用的情况需要按照实际情况来。

有一个问题,为什么每次创建 SqlSession 缓存就不能共用了?

因为每次在调用 SqlSessionFactory 的 openSession() 都会创建 Executor 实例,但是 BaseExecutor 是 SimpleExecutor 的父类,CachingExecutor 通过责任链模式包装了 SimpleExecutor,所以,新建了 SqlSession 就不能使用之前的缓存了。

相关推荐
2501_9418008811 小时前
Java高性能搜索引擎与Lucene实战分享:大规模文本索引、检索与优化经验
mybatis
大猫子的技术日记11 小时前
[百题重刷]前缀和 + Hash 表:缓存思想, 消除重复计算
java·缓存·哈希算法
愤怒的山羊13 小时前
jetcache List 缓存, json 序列化 泛型解析成了 JsonObject 处理
缓存·json·list
树在风中摇曳13 小时前
带哨兵位的双向循环链表详解(含 C 代码)+ LeetCode138 深度解析 + 顺序表 vs 链表缓存机制对比(图解 CPU 层级)
c语言·链表·缓存
q***428215 小时前
SpringCloud-持久层框架MyBatis Plus的使用与原理详解
spring·spring cloud·mybatis
北郭guo16 小时前
MyBatis框架讲解,工作原理、核心内容、如何实现【从浅入深】让你看完这篇文档对于MyBatis的理解更加深入
java·数据库·mybatis
斯文~17 小时前
「玩透ESA」站点配置阿里云ESA全站加速+自定义规则缓存
阿里云·缓存·云计算·cdn·esa
S***t71417 小时前
Python装饰器实现缓存
缓存
天硕国产存储技术站17 小时前
3000次零失误验证,天硕工业级SSD筑牢国产SSD安全存储方案
缓存·固态硬盘·国产ssd
前端炒粉21 小时前
35.LRU 缓存
开发语言·javascript·数据结构·算法·缓存·js