MyBatis
缓存
MyBatis跟缓存相关的类都在cache包里面,其中有一个Cache接口,只有一个默认的实现类PerpetualCache,它是用HashMap实现的。
一级缓存
一级缓存也叫本地缓存,
MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis的一级缓存是默认开启的,不需要任何的配置。 同一个SqlSession对象, 在参数和SQL完全一样的情况下, 只执行一次SQL语句(如果缓存没有过期)。
<!-- 默认为 `SESSION` -->
<setting name="localCacheScope" value="SESSION|STATEMENT"/>
SqlSession 一级缓存的工作流程:
- 对于某个查询,根据
statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出缓存结果 - 判断从
Cache中根据特定的key值取的数据数据是否为空,即是否命中 - 如果命中,则直接将缓存结果返回
- 如果没命中
- 去数据库中查询数据,得到查询结果
- 将
key和查询到的结果分别作为key,value对存储到Cache中 - 将查询结果返回
- 不同的
SqlSession之间的缓存是相互隔离的,多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement - 一级缓存无法关闭,在作用域配置为
SESSION时,可以通过配置flushCache="true"将作用域设为statement,使得在查询后清空缓存 - 任何的
UPDATE,INSERT,DELETE语句都会清空缓存
二级缓存
二级缓存是用来解决一级缓存 不能跨会话共享的问题的,范围是
namespace级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步(SqlSessionFactory生命周期)。如果开启了二级缓存,并且Mapper和SELECT语句也配置使用了二级缓存,那么在执行查询的时候,会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:二级缓存 -> 一级缓存 -> 数据库。
<cache type="org.apache.ibatis.cache.impl.PerpetualCache" size="1024" eviction="LRU" flushInterval="120000" readOnly="false"/>
- 二级缓存是事务性的,在执行查询时,只有完成事务提交或回滚,缓存才会获得更新。
- 因为更新会刷新缓存,导致二级缓存失效,所以二级缓存适合在查询为主的应用中使用
- 如果多个
namespace中有针对于同一个表的操作,在一个namespace中刷新了缓存,另一个namespace中没有刷新,就会出现读到脏数据的情况,所以推荐在一个Mapper里面只操作单表的情况下使用 Mapper.xml配置了<cache>后,SELECT会被缓存,UPDATE、DELETE、INSERT会刷新缓存;如果Mapper.xml没有配置<cache>,则该namespace没有二级缓存,但依然会使用CachingExecutor包装对象执行sql- 如果某些查询方法对数据的实时性要求很高,不需要二级缓存,可以在单个
Statement上通过useCache="false"显式关闭二级缓存(默认为true) - 如果要让多个
namespace共享一个二级缓存,可以使用<cache-ref>来解决
<cache-ref namespace="com.wuzz.crud.dao.DepartmentMapper" />
其他
mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。
- 传入的
statementId - 查询时要求的结果集中的结果范围
- 查询所产生的最终要传递给
statement的SQL语句字符串 - 传递给
statement的参数值
插件
Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK 动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能。 实现Mybatis的Interceptor接口并重写intercept方法,在Interceptor类上使用@Intercepts注解指定要拦截的类,方法,参数类型即可。