目录
验证代码如下
mappper 代码
xml 中代码
实际执行代码
执行结果
DefaultSqlSession
CachingExecutor
BaseExecutor
PerpetualCache
总结
禁用一级缓存
mapper 对应的 xml 的 select 查询设置 flushCache 属性为 true
MappedStatement 的内部类 Builder 向外部变量 flushCacheRequired 赋值
MapperBuilderAssistant 的 setStatementCache()
XMLStatementBuilder 的 parseStatementNode() 解析 mapper 中定义的 sql
mybatis 全局配置 settings 中添加 name 为 localCacheScope 的节点,对应 value 为 STATEMENT
XMLConfigBuilder的settingsElement()方法
有一个问题,为什么每次创建 SqlSession 缓存就不能共用了?
在之前的文章基础上
https://blog.csdn.net/zlpzlpzyd/article/details/135171524
package cn.hahaou.mybatis.cache.levelone.mapper;
import cn.hahaou.mybatis.cache.levelone.entity.Role;
public interface LevelOneRoleMapper {
Role getRole(Long id);
}
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();
}
}
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的实现类调用了 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()
在这里有两件事,创建 CacheKey 和调用嵌套的 SimpleExecutor 执行查询。其中 CacheKey 用于接下来缓存查询结果使用。
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 中。
数据最终存储到了 map 对象中。
可以看到,通过一个简单的查询,mybatis 使用了责任链模式,通过内存缓存当前查询结果,但是这个只适用于那种单体应用,分布式应用的话使用一级缓存不太好,有缓存不一致的问题。
如果数据量大的话会造成内存溢出的情况发生。
所以,针对项目部署的是集群环境,不要用一级缓存。如果是单体数据量不大可以使用。
可知,有两种方式
setStatementCache() 的上层调用方法 addMappedStatement()
可以看到,如果 SqlCommandType 值为 SELECT,flushCache 的值的情况如下
如果 flushCache 的值未设置,flushCache 值为 false,默认使用缓存。
如果 flushCache 的值设置为 true,flushCache 值为 true,禁止使用缓存。
其中 localCacheScope 实际对应的是枚举类型 LocalCacheScope,只有两个值,默认值为 SESSION。
package org.apache.ibatis.session;
/**
* @author Eduardo Macarron
*/
public enum LocalCacheScope {
SESSION,STATEMENT
}
如果指定了其他值,创建 SqlSessionFactory 的过程中会出现异常。
这两种方式,要讲哪种方式好,还是全局方式好,当然,对于一级缓存禁用的情况需要按照实际情况来。
因为每次在调用 SqlSessionFactory 的 openSession() 都会创建 Executor 实例,但是 BaseExecutor 是 SimpleExecutor 的父类,CachingExecutor 通过责任链模式包装了 SimpleExecutor,所以,新建了 SqlSession 就不能使用之前的缓存了。