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

目录

mybatis 二级缓存配置有两处

全局设置

mapper 设置

测试代码

执行结果

源码执行逻辑

创建 SqlSession

二级缓存配置是否添加

解析 cache 标签

XMLMapperBuilder

MapperBuilderAssistant

CacheBuilder

PerpetualCache

SerializedCache

LoggingCache

将 cache 对象添加到 MappedStatement 对象中

XMLStatementBuilder

MapperBuilderAssistant

查询数据

调用 CachingExecutor 的 query()

调用 TransactionalCacheManager 的 getObject()

调用 SqlSession 的 commit()

调用 CachingExecutor 的 commit()

调用 TransactionalCacheManager 的 commit()

调用 TransactionalCache 的 commit()

缓存失效

执行非 select 操作

XMLStatementBuilder

在 mapper 的 select 标签中设置 flushCache 为 true

CachingExecutor

缓存禁用

xml 中去掉  标签

全局配置

总结


在之前写的 mybatis 文章基础上

mybatis的生命周期-CSDN博客

mybatis的一级缓存使用以及禁用-CSDN博客

mybatis 二级缓存配置有两处

两个一起设置才能生效

全局设置





    
    

    
        
        
        
    

    
    
        
    

    
    
        
            
            
            
            
                
                
                
                
            
        
    

    
    
        
    

mapper 设置





    
    

    
        
        
        
    

    

测试代码

package cn.hahaou.mybatis.cache.leveltwo;

import cn.hahaou.mybatis.cache.leveltwo.mapper.LevelTwoRoleMapper;
import cn.hahaou.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;

/**
 * 二级缓存测试
 */
public class LevelTwoCacheTest {

    public static void main(String[] args) {
        {
            try (SqlSession sqlSession = MybatisUtils.openSession()){
                LevelTwoRoleMapper roleMapper = sqlSession.getMapper(LevelTwoRoleMapper.class);
                roleMapper.getRole(1L);
                sqlSession.commit();
            }
        }
        System.out.println("开启新session查询");
        {
            try (SqlSession sqlSession = MybatisUtils.openSession()){
                LevelTwoRoleMapper roleMapper = sqlSession.getMapper(LevelTwoRoleMapper.class);
                roleMapper.getRole(1L);
                sqlSession.commit();
            }
        }
    }
}

执行结果

2023-12-24 16:32:10,019 [main] DEBUG org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
2023-12-24 16:32:10,100 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
2023-12-24 16:32:10,101 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
2023-12-24 16:32:10,101 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
2023-12-24 16:32:10,101 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
2023-12-24 16:32:10,148 [main] DEBUG org.apache.ibatis.cache.decorators.LoggingCache: Cache Hit Ratio [cn.hahaou.mybatis.cache.leveltwo.mapper.LevelTwoRoleMapper]: 0.0
2023-12-24 16:32:10,150 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
2023-12-24 16:32:10,347 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 510109769.
2023-12-24 16:32:10,348 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1e67a849]
2023-12-24 16:32:10,355 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: select * from t_role t where t.id = ? 
2023-12-24 16:32:10,374 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Long)
2023-12-24 16:32:10,395 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==      Total: 1
2023-12-24 16:32:10,401 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1e67a849]
2023-12-24 16:32:10,407 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1e67a849]
2023-12-24 16:32:10,407 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 510109769 to pool.
开启新session查询
2023-12-24 16:32:10,409 [main] DEBUG org.apache.ibatis.cache.decorators.LoggingCache: Cache Hit Ratio [cn.hahaou.mybatis.cache.leveltwo.mapper.LevelTwoRoleMapper]: 0.5

从日志可以得知

第一个 session 查询缓存命中比率为 0,执行 jdbc 查询。

第一个 session 查询缓存命中比率为 0.5,不执行 jdbc 查询。

源码执行逻辑

创建 SqlSession

mybatis的二级缓存使用以及禁用_第1张图片

先通过 Configuration 创建 Executor

mybatis的二级缓存使用以及禁用_第2张图片

Configuration 中的变量 cacheEnabled 对应的值是全局配置的 cacheEnabled 的值,如果未指定,默认值为 true。最终结果返回了一个 CachingExecutor,通过装饰器模式封装了一个 SimpleExecutor。

mybatis的二级缓存使用以及禁用_第3张图片

二级缓存配置是否添加

mybatis的二级缓存使用以及禁用_第4张图片

其中,参数 MappedStatement 对应的是每个 mapper 中定义的用于执行 sql 操作的方法。获取其中的 cache 对象判断是否为空,不为空执行二级缓存逻辑。

解析 cache 标签

XMLMapperBuilder

mybatis的二级缓存使用以及禁用_第5张图片

在 XMLMapperBuilder 中解析 mapper 中定义的 cache 标签。

mybatis的二级缓存使用以及禁用_第6张图片

通过代码得知如下

type 未指定,默认值为 PERPETUAL,对应的是 PerpetualCache,与一级缓存一致。

eviction 未指定,默认值为 LRU,对应的是 LruCache,即缓存淘汰策略使用了 lru 算法。

readOnly 未指定,默认值为 false,但是又对结果进行了取反,所以结果是 true。对用后面的 SerializedCache。

MapperBuilderAssistant

调用 useNewCache() 通过 CacheBuilder 链式编程创建 Cache 对象。

mybatis的二级缓存使用以及禁用_第7张图片

设置 Configuration 的缓存对象为链式创建的。

CacheBuilder

mybatis的二级缓存使用以及禁用_第8张图片

通过 setDefaultImplementations() 得知,如果在调用 useNewCache() 创建 Cache 对象时变量typeClass 和 evictionClass 未指定,分别指定 PerpetualCache 和 LruCache。

在 build() 中循环当前类的 decorators 对象,对 PerpetualCache 使用装饰器模式包装。第一层为 LruCache。

在 setStandardDecorators() 对传入的 Cache 对象(实际为 PerpetualCache)进行装饰器模式包装处理。

mybatis的二级缓存使用以及禁用_第9张图片

鉴于是默认处理,所有最终的 cache 对象如下,中间通过内部变量 delegate 来进行引用。

SynchronizedCache
↓
LoggingCache 计算命中率
↓
SerializedCache 需要缓存的数据需要序列化
↓
LruCache 缓存淘汰策略
↓
PerpetualCache 最终保存缓存的对象

PerpetualCache

最终存储的数据如下

id 为 mapper 全路径。一级缓存是常量字符串。

map 变量 cache 中,key 对应的是 CacheKey,value 对应的是返回的查询结果序列化后的字节数组。一级缓存是返回结果没做任何处理。

mybatis的二级缓存使用以及禁用_第10张图片

 鉴于最终存储的结果是序列化后的字节数组,所以返回的对象需要实现序列化接口 Serializable。

SerializedCache

mybatis的二级缓存使用以及禁用_第11张图片

LoggingCache

mybatis的二级缓存使用以及禁用_第12张图片

针对每次查询请求对变量值进行计数累加,如果查询的数据在缓存中存在,命中数进行累加。

 mybatis的二级缓存使用以及禁用_第13张图片

命中率计算=命中数/请求总数。

将 cache 对象添加到 MappedStatement 对象中

在 XMLMapperBuilder 中间接调用 XMLStatementBuilder 进行 crud sql 解析处理

mybatis的二级缓存使用以及禁用_第14张图片

XMLStatementBuilder

根据当前查询类型为 select,默认启用缓存。

mybatis的二级缓存使用以及禁用_第15张图片

看到这里感觉很熟悉,因为上面的 flushCache 是一级缓存的标识。

MapperBuilderAssistant

调用 addMappedStatement() 添加到 Configuration  中。

mybatis的二级缓存使用以及禁用_第16张图片

设置 MappedStatement 中二级缓存相关相关的变量

useCache 表示启用二级缓存
cache 表示缓存对象

mybatis的二级缓存使用以及禁用_第17张图片

通过链式编程方式赋值最终构建 MappedStatement 对象。

查询数据

通过 TransactionalCacheManager 这个中间对象来完成。

调用 CachingExecutor 的 query()

mybatis的二级缓存使用以及禁用_第18张图片

在上图中,从 MappedStatement 中获取 cache 对象和 useCache 的值来判断是否启用二级缓存,从上面的分析可以得出,这里 useCache 的判断用于判断当前 statement 的 SqlCommandType 的值,如果为 SELECT 就启用,否则不启用。

调用 TransactionalCacheManager 的 getObject()

通过 getObject() 传入 cache 对象判断对应的 TransactionalCache 在  TransactionalCacheManager 中是否存在

mybatis的二级缓存使用以及禁用_第19张图片

通过 cache 对象判断在 map 对象 transactionalCaches 中是否存在,不存在使用 TransactionalCache 包装添加到 transactionalCaches 中。

因为使用的装饰器模式,最终调用的是 PerpetualCache 中的 cache 变量。

通过 putObject() 传入 cache 对象判断对应的 TransactionalCache 在  TransactionalCacheManager 中是否存在,存在添加到 TransactionalCache 的 entriesToAddOnCommit 变量中。

mybatis的二级缓存使用以及禁用_第20张图片

mybatis的二级缓存使用以及禁用_第21张图片

调用 SqlSession 的 commit()

mybatis的二级缓存使用以及禁用_第22张图片

间接调用 CachingExecutor 的 commit()。

调用 CachingExecutor 的 commit()

mybatis的二级缓存使用以及禁用_第23张图片

调用 TransactionalCacheManager 的 commit()

mybatis的二级缓存使用以及禁用_第24张图片

对于  transactionalCaches 中的 value 值调用对应的 commit() 方法。

调用 TransactionalCache 的 commit()

间接调用 flushPendingEntries()

mybatis的二级缓存使用以及禁用_第25张图片

对应上面调用 putObject() 时向 entriesToAddOnCommit 中添加,因为使用的装饰器模式,最终添加到 PerpetualCache 中的 cache 变量。

其中,对于二级缓存调用 SqlSession 的 close() 也可以做到,对于忘记提交的情况,这里体现了补救措施。

mybatis的二级缓存使用以及禁用_第26张图片

 mybatis的二级缓存使用以及禁用_第27张图片

上面的测试代码使用了 java 7 中带的 try-with-resources 功能,括号中的变量只能是 Closeable 或者 AutoCloseable 的实现类,在编译器编译时自动添加 close() 防止流关闭的情况。

java中的Closeable与AutoCloseable-CSDN博客

缓存失效

有两种方式

执行非 select 操作

即 insert、delete、update。

XMLStatementBuilder

mybatis的二级缓存使用以及禁用_第28张图片

在执行非查询操作时,useCache 的值为 false,由于该值最终在 MappedStatement 的 useCache 中进行赋值,最终在 CachingExecutor 中会对该值进行判断,如果为 false,不会走缓存的逻辑,会导致缓存失效。

在 mapper 的 select 标签中设置 flushCache 为 true

CachingExecutor

mybatis的二级缓存使用以及禁用_第29张图片

mybatis的二级缓存使用以及禁用_第30张图片

通过上面的分析得知,二级缓存添加是通过 TransactionalCacheManager 的 commit() 来完成的,所以将 flushCache 设置为 false 后,删除 TransactionalCacheManager 中 transactionalCaches 的变量值,导致添加的 TransactionalCache 被清空,所以需要再次查询数据。

缓存禁用

有两种方式

xml 中去掉  标签

作用是 mapper 层级

全局配置

默认值为 true,如果设置为 false,mapper 设置后将不起作用。

mybatis 中将二级缓存设置了一半(Configuration 中 cacheEnabled 值为 true),另外一半需要使用者自己处理(mapper 中需要添加 标签进行自定义参数处理,如果不指定参数有默认参数)。

其实二级缓存最终通过 CachingExecutor 来实现,如下图,全局配置在这里进行逻辑处理,如果没配置相关的,则在创建 SqlSession 时最终返回的是 SimpleExecutor。

mybatis的二级缓存使用以及禁用_第31张图片

总结

一级缓存针对的是 SqlSession 层次,二级缓存针对的是 mapper 层次。

最终的缓存类都是 PerpetualCache,只是二级缓存通过装饰器模式串联了多个 cache 实现,可以针对不同的功能串联不同的 cache 实现。

一级缓存和二级缓存都有一个缺点,无法解决缓存共用的问题。所以,针对集群项目不建议使用一级缓存和二级缓存,最好禁用。

你可能感兴趣的:(mybatis,缓存,mybatis,缓存)