目录
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);
}
<?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>
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。
调用了 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 使用了责任链模式,通过内存缓存当前查询结果,但是这个只适用于那种单体应用,分布式应用的话使用一级缓存不太好,有缓存不一致的问题。
如果数据量大的话会造成内存溢出的情况发生。
所以,针对项目部署的是集群环境,不要用一级缓存。如果是单体数据量不大可以使用。
可知,有两种方式
<select flushCache="true" id="getRole" parameterType="long" resultType="role">
select * from t_role t where t.id = #{id}
</select>
setStatementCache() 的上层调用方法?addMappedStatement()
可以看到,如果?SqlCommandType 值为?SELECT,flushCache 的值的情况如下
如果 flushCache 的值未设置,flushCache 值为 false,默认使用缓存。
如果 flushCache 的值设置为 true,flushCache 值为 true,禁止使用缓存。
<setting name="localCacheScope" value="STATEMENT"/>
其中 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 就不能使用之前的缓存了。