正如大多数持久层框架同样,MyBatis 一样提供了一级缓存和二级缓存的支持 php
可能你会有疑惑,个人mybatis bean是由spring 来管理的,已经屏蔽了sqlSession这个东西了?那怎么的一次操做才算是一次sqlSession呢?java
spring整合mybatis后,非事务环境下,每次操做数据库都使用新的sqlSession对象。所以mybatis的一级缓存没法使用(一级缓存针对同一个sqlsession有效)redis
在开启事物的状况之下,spring使用threadLocal获取当前资源绑定同一个sqlSession,所以此时一级缓存是有效的 在开启以及缓存的时候查询获得的对象是同一个对象。 这种状况下会出现一个问题。咱们先看一下代码。算法
public void listMybatisModel() {
List<MybatisModel> mybatisModels = mapper.listMybatisModel();
List<MybatisModel> mybatisModelsOther = mapper.listMybatisModel();
System.out.println(mybatisModels == mybatisModelsOther);
System.out.println("list count: " + mybatisModels.size());
}
复制代码
System.out.println(mybatisModels == mybatisModelsOther);
复制代码
输出结果居然是true,这样说来是同一个对象。 会出现这种场景,第一次查出来的对象而后修改了,第二次查出来的就是修改后的对象。spring
对SqlSession的操做mybatis内部都是经过Executor来执行的。Executor的生命周期和SqlSession是一致的。Mybatis在Executor中建立了一级缓存,基于PerpetualCache 类的 HashMapsql
用下面这张图描述一级缓存和二级缓存的关系。数据库
// mybatis-config.xml 中配置
<settings>
默认值为 true。即二级缓存默认是开启的
<setting name="cacheEnabled" value="true"/>
</settings>
// 具体mapper.xml 中配置
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
<!-- 开启本mapper的namespace下的二级缓存 type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache 要和ehcache整合,须要配置type为ehcache实现cache接口的类型-->
<cache />
</mapper>
复制代码
若是想要设置增删改操做的时候不清空二级缓存的话,能够在其insert或delete或update中添加属性flushCache=”false”,默认为 true。apache
<delete id="deleteStudent" flushCache="false">
DELETE FROM user where id=#{id}
</delete>
复制代码
经过如下一些配置能够修改一些缓存参数。缓存
<cache eviction= "FIFO" flushlnterval="600000" size="512" readOnly="true" />
复制代码
配置建立了一个FIFO缓存,并每隔 60 秒刷新一次,存储集合或对象的512个引用, 并且返回的对象被认为是只读的,所以在不一样线程中的调用者之间修改它们会致使冲突。cache能够配置的属性以下。 eviction (收回策略)安全
LRU (最近最少使用的: 移除最长时间不被使用的对象,这是默认值 。 FIFO (先进先出〉 : 按对象进入缓存的顺序来移除它们 。 SOFT (软引用) : 移除基于垃圾回收器状态和软引用规则的对象 。 WEAK (弱引用) : 更积极地移除基于垃圾收集器状态和弱引用规则的对象
@CacheNamespace (
eviction = FifoCache.class ,
flushinterval = 60000 ,
size = 512 ,
readWrite = true)
public interface Mapper {
}
复制代码
括号内的内容是配置缓存属性。
@CacheNarnespaceRef(RoleMapper.class)
public interface RoleMapper {
复制代码
由于想让 RoleMapper 接口中的注解方法和 XML中的方法使用相同的缓存,所以使用参照缓存配置RoleMapper.class,这样就会使用命名空间为xx.xxx.xxx.xxx.RoleMapper的缓存配置,即RoleMapper.xml 中配置的缓存 。 Mapper 接口能够经过注解引用XML 映射文件或者其余接口的缓存,在 XML 中也能够配置参照缓存,如能够在 RoleMapper.xml 中进行以下修改 。
<cache-ref narnespace="xxx.xxx.xxx.xxx.RoleMapper"/>
复制代码
这样配置后XML 就会引用 Mapper 接口中配置的二级缓存,一样能够避免同时配置二级缓存致使的冲突。MyBatis 中不多会同时使用 Mapper 接口注解方式和XML映射文件,因此参照缓存并非为了解决这个问题而设计的。参照缓存除了可以经过引用其余缓存减小配置外,主要的做用是解决脏读。
MyBatis使用SerializedCache(org.apache.ibaits.cache.decorators.SerializedCache)序列化缓存来实现可读写缓存类,井经过序列化和反序列化来保证经过缓存获取数据时,获得的是一个新的实例。所以,若是配置为只读缓存,MyBatis就会使用Map来存储缓存值,这种状况下,从缓存中获取的对象就是同一个实例。由于使用可读写缓存,可使用SerializedCache序列化缓存。这个缓存类要求全部被序列化的对象必须实现 Serializable (java.io.Serializable)接口 虽然使用序列化获得的对象都是不同的对象修改时都是互不影响,可是仍是不安全的。
Mybatis的二级缓存是和命名空间绑定的,因此一般状况下每个Mapper映射文件都有本身的二级缓存,不一样的mapper的二级缓存互不影响。
缓存数据有内存和磁盘两级,无须担忧容量问题。
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.3</version>
</dependency>
复制代码
在 src/main/resources 目录下新增 ehcache.xml 文件。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true">
<diskStore path="D:/cache" />
<defaultCache maxElementsInMemory="3000" eternal="false" copyOnRead="true" copyOnWrite="true" timeToIdleSeconds="3600" timeToLiveSeconds="3600" overflowToDisk="true" diskPersistent="true"/>
</ehcache>
复制代码
有关EhCache的详细配置能够参考地址 www.ehcache.org/ehcache.xml 中的内容。
在xml中添加
<cache type ="org.mybatis.caches.ehcache.EhcacheCache" />
复制代码
只经过设置 type 属性就可 以使用 EhCache 缓存了,这时cache的其余属性都不会起到任何做用,针对缓存的配置都在ehcache.xml中进行。在ehcache.xml配置文件中,只有一个默认的缓存配置,因此配置使用EhCache缓存的Mapper映射文件都会有一个以映射文件命名空间命名的缓存。若是想针对某一个命名空间进行配置,须要在 ehcache.xml 中添加一个和映射文件命名空间一致的缓存配置,例如针对RoleMapper能够进行以下配置。
<cache name="tk.mybatis.simple.mapper.RoleMapper" maxElementsInMemory="3000" eternal="false" copyOnRead="true" copyOnWrite="true" timeToIdleSeconds="3600" timeToLiveSeconds="3600" overflowToDisk="true" diskPersistent="true"/>
复制代码
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
复制代码
host=localhost
port=6379
connectionTimeout=SOOO
soTimeout=SOOO
password=
database=O
clientName=
复制代码
<mapper namespace = ” tk.mybat 工 s.s 工 mple.mapper.RoleMapper ” 〉
<cache type= "org.mybatis.caches.redis.RedisCache" />
〈 !一其余本身直一 〉
</mapper>
复制代码
配置依然很简单, RedisCache 在保存缓存数据和获取缓存数据时,使用了Java的序列化和反序列化,所以还须要保证被缓存的对象必须实现Serializable接口。改成RedisCache缓存配置后, testL2Cache 测试第一次执行时会所有成功,可是若是再次执行,就会出错。这是由于Redis做为缓存服务器,它缓存的数据和程序(或测试)的启动无关,Redis 的缓存并不会由于应用的关闭而失效。因此再次执行时没有进行一次数据库查询,全部查询都使用缓存,测试的第一部分代码中的rolel和role2都是直接从二级缓存中获取数据,由于是可读写缓存,因此不是相同的对象。当须要分布式部署应用时,若是使用MyBatis自带缓存或基础的EhCahca缓存,分布式应用会各自拥有本身的缓存,它们之间不会共享缓存 ,这种方式会消耗更多的服务器资源。若是使用相似 Redis 的缓存服务,就能够将分布式应用链接到同一个缓存服务器,实现分布式应用间的缓存共享 。