目录
[mappper 代码](#mappper 代码)
[xml 中代码](#xml 中代码)
[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 就不能使用之前的缓存了。