一级缓存
Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只在同SqlSession内有效。在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用同一个Mapper方法,往往只执行一次SQL,因为第一次查询后,MyBatis会将结果放入缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会去查询数据库。
每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。具体实现类的类关系图如下图所示。
生命周期
MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象
当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉
如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用
如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是SqlSession对象仍可使用。
SqlSession中执行了任何一个DML操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用
工作流程

使用缓存条件
mybatis认为对于两次查询,如果以下条件都完全一样,那就认为它们是完全相同的两次查询:
传入的statementId
查询时要求的结果集中的结果范围
这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串
传递给java.sql.Statement要设置的参数值
配置
在MyBatis的配置文件中开启一级缓存
value有两个选项:
- SESSION(默认):即在同一个会话中执行的所有语句,都会共享这一个缓存
- STATEMENT:即只对当前执行的这一个Statement有效(相当于无缓存机制)
1 | <setting name="localCacheScope" value="SESSION"/> |
场景测试
场景一:同一数据库会话中,可以看到仅第一次查询了数据库,后面两次都是从一级缓存获取结果
场景二:同一数据库会话中,两次查询中间执行了一次插入语句,第二次查询时导致一级缓存失效,重新查询数据库
场景三:开启两个会话SqlSession(A和B),当会话B更新了数据之后,会话A并没有读取到新的数据,还是从会话A的缓存获取结果,相反会话B读取到了新的数据,因为更新操作会把会话B缓存清空,会话B重新从数据库获取数据。说明一级缓存只在同一数据库会话内部共享。
小结
- 一级缓存在同一个SqlSession下共用缓存
- 一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺
- 一级缓存再有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement
二级缓存
一级缓存其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
配置
在MyBatis的配置文件中开启二级缓存
1 | <setting name="cacheEnabled" value="true"/> |
XML文件配置使用二级缓存,例如:
1 | <!--开启本mapper的namespace下的二级缓存--> |

场景测试
场景一:关闭了一级缓存的前提下,可以看到会话A第二次查询也没能通过缓存获取数据,因为事物未提交;当关闭了会话A后,会话B第二次查询使用了会话A存下来的缓存。结论:提交完事务后,二级缓存才能对所有会话生效(包括自己)
场景二:查询和更新分别在两个不同的namspace,可以看到即使会话C更新且提交了会话,但并没有清除前面查询留下的缓存,导致会话B第二次还是使用了缓存。结论:由于MyBatis的二级缓存是基于namespace的,查询语句所在的namspace无法感应到其他namespace中的语句执行的修改,引发脏数据问题。
解决方法:在更新的namspace中关联引用查询的namspace,这样两个映射文件对应的Sql操作就能使用的是同一块缓存了
小结
- 二级缓存在同一个namespace共用缓存
- 二级缓存实现了不同SqlSession之间缓存数据的共享,可以通过Cache接口实现类不同的组合,对Cache的可控性也更强
- 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻
- 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高