1. 前言
MyBatis是Java中常用的数据层ORM框架,笔者目前在实际的开发中,也在使用MyBatis。本文主要介绍了MyBatis的缓存策略、以及基于SpringBoot和Redis实现MyBatis的二级缓存的过程。实现本文的demo,主要依赖以下软件版本信息,但是由于数据层面的实现,并不依赖具体的版本,你可以以自己主机当前的环境创建。
软件环境 | 版本 |
---|---|
SpringBoot | 1.5.18 |
Redis | 通用 |
MyBatis | 3.4.+ |
2. MyBatis缓存策略
2.1 一级缓存
默认基础接口有两个:
- org.apache.ibatis.session.SqlSession: 提供了用户和数据库交互需要的所有方法,默认实现类是DefaultSqlSession。
- org.apache.ibatis.executor.Executor: 和数据库的实际操作接口,基础抽象类BaseExecutor。
我们从底层往上查看源代码,首先打开BaseExecutor的源代码,可以看到Executor实现一级缓存的成员变量是PerpetualCache对象。
1 | /** |
我们再打开PerpetualCache类的代码:
1 | /** |
可以看到PerpetualCache是对Cache的基本实现,而且通过内部持有一个简单的HashMap实现缓存。
了解了一级缓存的实现后,我们再回到入口处,为了你的sql语句和数据库交互,MyBatis首先需要实现SqlSession,通过DefaultSqlSessionFactory实现SqlSession的初始化的过程可查看:
1 | private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { |
从代码中可以看到,通过configuration创建一个Executor,实际创建Executor的过程如下:
1 | public Executor newExecutor(Transaction transaction, ExecutorType executorType) { |
注意,cacheEnabled字段是二级缓存是否开启的标志位,如果开启,会使用使用CahingExecutor装饰BaseExecutor的子类。
创建完SqlSession,根据Statment的不同,会使用不同的SqlSession查询方法:
1 |
|
SqlSession把具体的查询职责委托给了Executor,如果只开启了一级缓存的话,首先会进入BaseExecutor的query方法。代码如下所示:
1 |
|
query方法实现了缓存的查询过程,在query方法执行的最后,会判断一级缓存级别是否是STATEMENT级别,如果是的话,就清空缓存,这也就是STATEMENT级别的一级缓存无法共享localCache的原因。
SqlSession的insert方法和delete方法,都会统一走update的流程,在BaseExecutor实现的update方法中:
1 |
|
可以看到,每次执行update方法都会执行clearLocalCache清空缓存。至此,我们分析完了MyBatis的一级缓存从入口到实现的过程。
关于MyBatis一级缓存的总结:
- 一级缓存的生命周期和SqlSession保持一致;
- 一级缓存的缓存通过HashMap实现;
- 一级缓存的作用域是对应的SqlSession,假如存在多个SqlSession,写操作可能会引起脏数据。
2.2 二级缓存
在上一小节中,我们知道一级缓存的的作用域就是对应的SqlSession。若开启了二级缓存,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,二级缓存的查询流程如图所示:
二级缓存开启后,同一个namespace下的所有数据库操作语句,都使用同一个Cache,即二级缓存结果会被被多个SqlSession共享,是一个全局的变量。当开启二级缓存后,数据查询的执行流程就是二级缓存 -> 一级缓存 -> 数据库。
二级缓的实现源码,可以查看CachingExecutor类的query方法:
1 |
|
在二级缓存查询结束后,就会进入一级缓存的执行流程,可参考上一小节内容。
关于二级缓存的总结:
- 二级缓存是SqlSession之间共享,能够做到mapper级别,并通过Cache实现缓存。
- 由于MyBatis的缓存都是内存级的,在分布式环境下,有可能会产生脏数据,因此可以考虑使用第三方存储组件,如Redis实现二级缓存的存储,这样的安全性和性能也会更高。
3. SpringBoot和Redis实现MyBatis二级缓存
MyBatis的默认实现一级缓存的,二级缓存也是默认保存在内存中,因此当分布式部署你的应用时,有可能会产生脏数据。通用的解决方案是找第三方存储缓存结果,比如Ehcache、Redis、Memcached等。接下来,我们介绍下,使用Redis作为缓存组件,实现MyBatis二级缓存。
在实现二级缓存之前,我们假设你已经实现了SpringBoot+MyBatis的构建过程,如果还没有,建议你先创建一个demo实现简单的CRUD过程,然后再查看本文解决二级缓存的问题。
3.1 增加Redis配置
首先在你的工程加入Redis依赖:
1 | compile('org.springframework.boot:spring-boot-starter-data-redis') |
我使用的gradle,使用maven的同学可对应查询即可!
其次在配置文件中加入Redis的链接配置:
1 | spring.redis.cluster.nodes=XXX:port,YYY:port |
这里我们使用的是Redis集群配置。
打开mybatis.xml配置文件,开启二级缓存:
1 | <setting name="cacheEnabled" value="true"/> |
增加Redis的配置类,开启json的序列化:
1 | import com.fasterxml.jackson.annotation.JsonAutoDetect; |
3.2 实现MyBatis的Cache接口
org.apache.ibatis.cache.Cache接口是MyBatis通用的缓存实现接口,包括一级缓存和二级缓存都是基于Cache接口实现缓存机制。
创建MybatisRedisCache类,实现Cache接口:
1 | import org.apache.ibatis.cache.Cache; |
由于redisTemplate是类变量,需要手动注入,再创建一个配置类注入redisTemplate即可:
1 | /** |
3.3 mapper文件中加入二级缓存的声明
在任意需要开启二级缓存的mapper配置文件中,加入:
1 | <!-- mapper开启二级缓存 --> |
至此,就完成了基于Redis的MyBatis二级缓存的配置。
4. FAQ
- 二级缓存相比较于一级缓存来说,粒度更细,但是也会更不可控,安全使用二级缓存的条件很难。
- 二级缓存非常适合查询热度高且更新频率低的数据,请谨慎使用。
- 建议在生产环境下关闭二级缓存,使得MyBatis单纯作为ORM框架即可,缓存使用其他更安全的策略。
以上内容就是关于SpringBoot基于Redis实现MyBatis查询缓存解决方案的全部内容了,谢谢你阅读到了这里!
Author:zhaoyh