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 就不能使用之前的缓存了。

相关推荐
草莓熊Lotso2 分钟前
【Redis 初阶】Hash 类型深度解析:结构化数据存储的最优解
linux·网络·数据库·redis·tcp/ip·缓存·哈希算法
kyle~2 小时前
计算机系统 --- 缓存一致性
开发语言·c++·缓存·计算机系统
深念Y14 小时前
SSD 寿命洁癖:编译过程不入盘的原则与实践
缓存·io·内存·编译·ssd·读取·写入
智购科技无人售货机厂家14 小时前
2026自动售货机实时库存管理系统:从Redis缓存到持久化的数据架构演进~YH
redis·缓存·架构
晓晓_za89866814 小时前
Geo 优化源码蒸馏词机制:地域词库构建与匹配逻辑详解
运维·tcp/ip·spring·缓存·ci/cd
nvd1114 小时前
LiteLLM 如何使用 Redis:从部署位置到精确响应缓存
redis·缓存·gateway
山甫aa16 小时前
Javaweb---MyBatis增删改查
java·数据库·后端·mybatis·springboot
choumou_M21 小时前
SpringBoot_2:MyBatis-Generator插件使用教程
spring boot·tomcat·mybatis
SQL-First布道者1 天前
全面解构传统持久层框架,拥抱真正的 SQL-First
java·数据库·spring boot·sql·spring·mybatis·spring jdbc
quantdash_cc1 天前
历史数据断层与REST请求慢到崩溃?QuantDash高性能量化数据API终极解决方案
开发语言·python·缓存·php·量化·quantdash