前言
mybatis作为一个ORM框架,缓存其实不是他的主要功能,作用也很有限。但要全面了解这个框架,这部分的知识也必不可少,下面就简单地总结下。
一级缓存
什么是一级缓存
一级缓存是SqlSession级别的,同一个SqlSession执行查询方法时会先从缓存中查询,如果查到就直接返回数据,不需要再查询数据库。缓存在SqlSession关闭时清空。当spring与mybatis整合后,每执行一个方法后,SqlSession都会关闭,这种情况一级缓存除了在事务下有用外,其它情况都没用了。
怎么用
mybatis一级缓存是默认开启的,在配置文件中添加如下语句就可以使用一级缓存,共有两个选项,SESSION和STATEMENT,默认是SESSION级别,即在一个Mybatis会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT级别,可以理解为缓存只对当前执行的这一个statement有效。
<settings>
<setting name="localCacheScope" value="SESSION" />
</settings>
代码验证
一级缓存生效,只查询数据库一次
中间增加插入操作,一级缓存失效
设置一级缓存为STATEMENT级别后
源码讨论
开启debug,看下执行一次查询究竟经历了什么~~
1、我们调用mapper接口方法,底层调的是mybatis为我们生成的代理对象方法,然后调用MapperMethod的execute方法
2、MapperMethod的execute方法里主要是一个swicth,用来根据方法的类型执行不同的操作。调用SqlSession的SelectOne
3、默认是调用SqlSession的实现DefaultSqlSession,selectOne底层是调用 selectList
4、selectList调用的是CashingExecutor(实现Executor接口)的query方法
5、query里又调了CashingExecutor另一个query重载方法
6、这里有一些缓存的操作,但这不是一级缓存,我们继续往下看,可以看到实际干活其实的是delegate(代表的意思,这里是SimpleExecutor,实现Executor)
7、SimpleExecutor继承BaseExecutor,一级缓存在BaseExecutor里实现,过程如下。这里可以看到如果我们在配置文件里配置一级缓存的级别为STATEMENT,就会清空缓存,相当于关闭一级缓存。
8、如果查询不到有缓存则会在queryFromDatabase里放入缓存
9、由于在DefaultSqlSession中insert,delete方法最后都是调用update,这里就以update为例,debug一下mapper的update方法,大概流程是一样,最后在执行BaseExecutor的update方法会清空缓存,所以在查询方法中间插入update方法,缓存会失效。
二级缓存
什么是二级缓存
二级缓存的范围比一级缓存要大,是namespace级别,同一个mapper文件下的方法共享。缺点就是当一个表有多个mapper文件操作时,很容易出现脏数据。
怎么用
mybatis的配置文件mybatis-config.xml里开启
<!-- 全局配置 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
要开启的xxMapper.xml里加入 标签
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.ysp.dao.UserDao">
<cache/>
代码验证
为避免一级缓存影响,关闭一级缓存,开启二级缓存。
二级缓存生效,Cache Hit Ratio(缓存命中率)为0.5
中间加入修改操作
二级缓存失效
下面验证下二级缓存脏数据的情况。
增加一个ROLE表,在UserMapper下增加关联查询方法getRoleInfoByUser,在RoleMapper里增加修改方法update,验证如下
可以看到查询出来了脏数据
在UserMapper里添加
<cache-ref namespace="org.ysp.mapper.RoleMapper"/>
如下图,UserMapper可以感知RoleMapper的修改。
源码讨论
这里讨论二级缓存的源码,有些细节将会被忽略,之后再深入研究。
二级缓存的代码在CachingExecutor的query方法里,这里得到一个SychronizedCache对象,由它来执行二级缓存。query调用TransactionCacheManager的getObject方法,SychronizedCache对象作为参数传入。
在TransactionCacheManager里,又调用TransactionCache的getObject方法,SychronizedCache对象作为参数传入。
TransactionCache里,可以看到取缓存和放缓存不是同一个对象,这里让我困惑了很久。后来发现原来是在调用调用SqlSession的commit方法时,将entriesToAddOnCommit里的对象放入缓存。
SqlSession commit方法最后也是调用TransactionCache的commit方法。
下面再看下查询中间插入update方法时的源码
当调用CachingExecutor的update时,通过flushCacheIfRequired方法会设置一个标志位clearOnCommit,也就是上图SqlSession commit方法那个标志位,为true则清空缓存。
最后还有一个问题,二级缓存的SychronizedCache是被不同SqlSession共享的,那SychronizedCache是从哪里来的?其实也不难想到,只能在创建sqlSessionFactory的时候。
总结
1、网上很多文章都总结得很好了,这里就一句话,安全的使用mybatis一二级缓存的条件较高,还是让她做一个单纯的ORM框架就好了。
2、这里说下我趟过的坑,试验二级缓存的时候实体类要实现Serializable接口,不然运行会报错。还有记得查询后记得调用commit方法,不然二级缓存会失效。