Mybatis的一二级缓存源码分析
一级缓存
介绍
同一个sqlSession或者statement里面,如果请求的语句参数一样,就会命中缓存直接返回,不会再去查询数据库。一旦数据库有修改,缓存则会失效。
mybatis:
configuration:
local-cache-scope: session ##一级缓存范围 有session和statement
原理讲解
测试代码:
@Test
public void test2(){
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println(mapper.getUserVoByUserId(String.valueOf(200001)));
System.out.println(mapper.getUserVoByUserId(String.valueOf(200001)));
}
返回结果:
15:50:37.525 [main] DEBUG c.r.c.m.UserMapper - [getObject,60] - Cache Hit Ratio [com.ruoyi.customer.mapper.UserMapper]: 0.0
15:50:37.525 [main] DEBUG c.r.c.m.U.getUserVoByUserId - [debug,137] - ==> Preparing: select tu.`user_id`, tu.`user_code`, tu.`customer_id`, tu.`user_name`, tu.`password`, tu.`type`, tu.`status`, tu.`encrypt_type`, tu.`encrypt_key`, tu.`hash_type`, tu.`post_num`, tu.`remark`, tu.create_by, tu.create_time, tu.update_by, tu.update_time, tc.simple_name `customer_simple_name`, tc.full_name `customer_full_name`, tc.area `area`, sda.dict_label `area_name`,sds.dict_label `status_name`, sdt.dict_label `type_name` from t_user tu left join t_customer tc on tu.customer_id = tc.customer_id left join sys_dict_data sda on tc.area = sda.dict_value left join sys_dict_data sds on tc.status = sds.dict_value left join sys_dict_data sdt on tc.type = sdt.dict_value WHERE sds.dict_type = 't_customer_status' and sda.dict_type = 'sys_zoon' and sdt.dict_type = 't_customer_type' and tu.user_id = ?
15:50:37.532 [main] DEBUG c.r.c.m.U.getUserVoByUserId - [debug,137] - ==> Parameters: 200001(String)
15:50:37.567 [main] DEBUG c.r.c.m.U.getUserVoByUserId - [debug,137] - <== Total: 1
UserVo(customerSimpleName=xxx, customerFullName=xxx, typeName=内部, statusName=正常, area=4, areaName=北区)
15:50:37.568 [main] DEBUG c.r.c.m.UserMapper - [getObject,60] - Cache Hit Ratio [com.ruoyi.customer.mapper.UserMapper]: 0.0
UserVo(customerSimpleName=xxx, customerFullName=xxx, typeName=内部, statusName=正常, area=4, areaName=北区)
如上所示只进行了一次查询,我们可以将返回的对象进行比较,会发现是同一个对象。
有三个基本的类:
SqlSession
: 用户与底层数据库的交互代码
Executor
: SqlSession操作数据库的代码
Cache
: 提供缓存的功能
/*
SqlSession
-> DefaultSqlSession
-> MybatisSqlSessionTemplate
-> SqlSessionManager
-> SqlSessionTemplate
*/
public interface SqlSession extends Closeable {
//根据statement查询出一条数据
<T> T selectOne(String statement);
//根据statement和parameter查询出一条数据
<T> T selectOne(String statement, Object parameter);
//根据statement查询出多条数据
<E> List<E> selectList(String statement);
//根据statement和parameter查询出多条数据
<E> List<E> selectList(String statement, Object parameter);
//根据statement和parameter和rowBounds界限查询出多条数据
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
/*
Executor
-> BaseExecutor
-> BatchExecutor
-> ClosedExecutor
-> ReuseExecutor
-> SimpleExecutor
-> CachingExecutor
*/
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
/*
Cache
-> PerpetualCache
-> BlockingCache
-> FifoCache
-> LoggingCache
-> LruCache
-> PerpetualCache
-> ScheduledCache
-> SerializedCache
-> SoftCache
-> SynchronizedCache
-> TransactionalCache
-> WeakCache
*/
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
以上的类就是实现一二级缓存的主要基类,下面我们DEBUG来讲解一级缓存一下。
DefaultSqlSessionFactory#openSession()
-> DefaultSqlSessionFactory#openSessionFromDataSource()
-> 创建Executor;final Executor executor = configuration.newExecutor(tx, execType);
-> Configuration#newExecutor()
-> 根据executor选型创建,默认是ReuseExecutor
-> 如果cacheEnabled是true,则使用装配器模式,构建CacheExecutor
-> 创建SqlSession; new DefaultSqlSession(configuration, executor, autoCommit)
MapperMethod#execute()执行方法
-> DefaultSession#selectOne()
-> DefaultSession#selectList()
-> BaseExecutor#query()
// BaseExecutor#query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//从Cache中获取缓存内容
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//如果缓存中的内容为空,则执行
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
//如果缓存级别为statement,则清空当前缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
优缺点
- 读取速度快
- 一级缓存的里面缓存cache只是一个HashMap,没有对其做容量、分类限制,缓存数据过多对性能有所影响。
- 一级缓存的范围是Session,如果系统部署是分布式的,同时对数据库某张表有操作,会造成脏数据的读取
二级缓存
介绍
多个Session之间可以进行共享的缓存,使用CachingExecutor装置BaseExecutor,先进行二级缓存,再进行一级缓存。
原理讲解
如上面所说,如果cacheEnabled是true(二级缓存开启),则使用装配器模式,构建CacheExecutor
下面就是先走 CachingExecutor#query,再走BaseExecutor#query
// CacheExecutor#query
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//先看一下mapper有没有缓存,如果mapper都没有缓存,那么相同操作的缓存更不可能存在
Cache cache = ms.getCache();
if (cache != null) {
//如果mapperStatement需要的话,就刷新缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//查看二级缓存事务中中有没有,如果有的话就需要再执行下面的
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//如果事务缓存中没有,则执行装饰器中的query,一般进入BaseExecutor#query
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//将查询结果放去缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
优缺点
- 多表关联查询的时候,容易出现脏数据
- 分布式的时候需要采用redis、memcached来作为缓存
实现redis来作为mybatis的二级缓存
public class MybatisRedisCache implements Cache {
private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);
private String id;
private RedisTemplate redisTemplate;
public MybatisRedisCache(String id) {
if (id == null){
throw new IllegalArgumentException("Cache instances require an ID");
}
logger.info("Redis Cache id " + id);
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
if (value != null){
redisTemplate.opsForValue().set(key.toString(),value,2, TimeUnit.DAYS);
}
}
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
if (key != null){
return redisTemplate.opsForValue().get(key.toString());
}
return null;
}
@Override
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
if (key != null){
redisTemplate.delete(key.toString());
}
return null;
}
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
Set keys = redisTemplate.keys("*" + this.id + "*");
if (!CollectionUtils.isEmpty(keys)){
redisTemplate.delete(keys);
}
}
@Override
public int getSize() {
return 0;
}
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null){
redisTemplate = SpringUtils.getBean("redisTemplate");
}
return redisTemplate;
}
}
//最后在需要使用缓存的mapper.xml文件配置一下
/*
<cache type="com.xxx.xxx.utils.MybatisRedisCache">
<property name="eviction" value="LRU" />
<property name="flushInterval" value="6000000" />
<property name="size" value="1024" />
<property name="readOnly" value="false" />
</cache>
*/
如果喜欢博主,可以关注我的公众号哈