MyBatis一般都是指二级缓存。一级缓存(也叫本地缓存)默认会启用,并且不能控制
7.1 一级缓存
@Test
public void testL1Cache() {
SqlSession sqlSession = getSqlSession();
SysUser user1 = null;
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
user1 = userMapper.selectById(1L);
user1.setUserName("chl");
SysUser user2 = userMapper.selectById(1L);
Assert.assertEquals("chl", user2.getUserName());
Assert.assertEquals(user1, user2);
}finally {
sqlSession.close();
}
System.out.println("开启新的sqlSession");
sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
SysUser user2 = userMapper.selectById(1L);
Assert.assertNotEquals("chl", user2.getUserName());
Assert.assertNotEquals(user1, user2);
userMapper.deleteById(2L);
SysUser user3 = userMapper.selectById(1L);
Assert.assertNotEquals(user2, user3);
}finally {
sqlSession.rollback();
sqlSession.close();
}
}
MyBatis的一级缓存存在于SqlSession的声明周期中,在同一个SqlSession查询时,MyBatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。如果同一个SqlSession中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map缓存对象中已经存在该键值时,则会返回缓存中的对象
进行如下修改,可以避免使用一级缓存
<select id="selectById" resultMap="userMap" flushCache="true">
select * from sys_user where id = #{id}
</select>
任何的insert、update、delete操作都会清空一级缓存
7.2 二级缓存
可以理解为存在于SqlSessionFactory的生命周期中
7.2.1 配置二级缓存
mybatis-config
<setting name="cacheEnabled" value="true"/>
该配置默认为true
MyBatis的二级缓存是和命名空间绑定的,即需要配置在Mapper.xml中或Mapper.java中
7.2.1.1 Mapper.xml中配置二级缓存
只需添加<cache/>
默认的二级缓存会有如下效果:
- select语句会被缓存
- insert、update、delete会刷新缓存
- 会用Least Recently Used(LRU)算法来回收
- 根据时间表,缓存不会以任何时间顺序来刷新
- 缓存会存储集合或对象的1024个引用
- 缓存会被视为read/write的,意味着对象检索不是共享的,而且可以安全地被调用者修改
所有这些属性都可以通过cache元素的属性来修改
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
7.2.1.2 Mapper接口中配置二级缓存
在使用注解方法时,如果想对注解方法启用二级缓存,则需要在Mapper接口中进行配置@CacheNamespace
当同时使用注解和xml方式时,不能同时配置上述的二级缓存,这时可以用参照缓存@CacheNamespaceRef(RoleMapper.class)
在xml中也可以配置参照缓存:<cache-ref namespace="com.ql.simple.mapper.RoleMapper"/>
,参照缓存不仅能引用其他缓存减少配置外,主要是为了解决脏读
7.2.2 使用二级缓存
如果配置的可读写缓存,MyBatis使用SerializedCache
序列化缓存来实现可读写缓存类
如果为只读缓存,MyBatis就会使用Map
来存储缓存值
当使用可读写缓存的时候,这个缓存类要求所有被序列化的对象必须实现Serializable
@Test
public void testL2Cache() {
SqlSession sqlSession = getSqlSession();
SysRole role1 = null;
try {
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
role1 = roleMapper.selectRoleById(1L);
role1.setRoleName("chl");
SysRole role2 = roleMapper.selectRoleById(1L);
Assert.assertEquals("chl", role2.getRoleName());
Assert.assertEquals(role1, role2);
}finally {
sqlSession.close();
}
System.out.println("开启新的SqlSession");
sqlSession = getSqlSession();
try {
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
SysRole role2 = roleMapper.selectRoleById(1L);
Assert.assertEquals("chl", role2.getRoleName());
Assert.assertNotEquals(role1, role2);
SysRole role3 = roleMapper.selectRoleById(1L);
Assert.assertNotEquals(role2, role3);
}finally {
sqlSession.close();
}
}
7.3 集成EhCache缓存
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.3</version>
</dependency>
<?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"/>
<cache
name="tk.mybatis.simple.mapper.RoleMapper"
maxElementsInMemory="3000"
eternal="false"
copyOnRead="true"
copyOnWrite="true"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="true"
diskPersistent="true"/>
</ehcache>
- copyOnRead: 判断缓存中读取数据是返回对象引用还是复制一个对象返回。默认为false,即返回数据引用
- copyOnWrite:判断写入缓存是直接缓存对象的引用还是复制一个对象缓存,默认为false,如果想使用可读写缓存,就需要将这两个属性设置为true
ehcache-cache提供了两个缓存实现:
- EhcacheCache
- LoggingEhcache
上面两个缓存使用时并没有区别,都会输出缓存命中率的日志
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
<cache
name="tk.mybatis.simple.mapper.RoleMapper"
maxElementsInMemory="3000"
eternal="false"
copyOnRead="true"
copyOnWrite="true"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="true"
diskPersistent="true"/>