一般提到MyBatis缓存的时候,都是指二级缓存。一级缓存( 也叫本地缓存〉默认会启用,并且不能控制,因此很少会提到。本文简单介绍MyBatis一级缓存,了解MyBatis一级缓存可以避免产生一些难以发现的错误。后面介绍MyBatis二级缓存,包括二级缓存的基本配置用法,还有一些常用缓存框架和缓存数据库的结合。除此之外还会介绍二级缓存的适用场景,以及如何避免产生脏数据。
先来一个示例,看看Mybatis一级缓存如何起作用:
public void testLlCache() {
// 获取SqlSession
SqlSession sqlSession = getSqlSession();
SysUser userl = null;
try {
//获取UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用selectByid 方法,查询id = 1 的用户
userl = userMapper.selectByid(lL);
//对当前获取的对象重新赋值
userl.setUserName(" New Name ");
//再次查询获取id 相同的用户
SysUser user2 = userMapper.selectByid(lL);
//虽然没有更新数据库,但是这个用户名和userl 重新赋值的名字相同
Assert.assertEquals(" New Name ", user2.getUserName());
// 无论如何, user 2 和userl 完全就是同一个实例
Assert.assertEquals(userl, user2);
} finally {
//关闭当前的sqlSessio 口
sqlSession.close();
System.out.println("开启新的sqlSession ");
//开始另一个新的session
sqlSession = getSqlSession();
try {
//获取UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用selectByid 方法,查询id = 1 的用户
SysUser user2 = userMapper.selectByid(lL);
// ;第二个session 获取的用户名仍然是admin
Assert.assertNotEquals(" New Name ", user2.getUserName());
//这里的us er2 和前一个session 查询的结采是两个不同的实例
Assert.assertNotEquals(userl, user2);
//执行7i11J 除操作
userMapper.deleteByid(2 L);
//获取user3
SysUser user3 = userMapper.selectByid(lL);
//这里的user2 和user3 是两个不同的实例
Assert.assertNotEquals(user2, user3);
} finally {
//关闭sqlSession
sqlSession.close();
}
}
}
查看日志可以知道:开启新sqlSession前的两次查询,user1查询了数据库,而第二句查询出来的user2,根本就没有查询,并且user2的值是user1设置后的值,所以user2只是use1r的引用。user1和user2是一个对象,原因是Mybatis的一级缓存。
Mybatis的一级缓存是存在于SqlSession的生命周期中,在同一个SqlSession中查询时,如果查询的方法名称和参数完全一致,先去缓存对象(Map对象)中查看,如果有查询缓存则会使用查询缓存中的对象返回。
如何清除一级缓存?
MyBatis的二级不同于一级缓存只存在于SqlSession 的生命周期中,而是可以理解为存在于SqlSessionFactory的生命周期中。缓存数据在一般情况下是不相通的。只有在使用如Redis这样的缓存数据库时,才可以共享缓存。
在Mybatis的全局配置settings中一个参数cacheEnabled属性,是二级缓存的全局开关(总开关),默认是true,也就是默认开启的。如果将其设置为false,即使后面再怎么配置二级缓存也不生效。不过因为是默认开启,所以可以不用设置。如果设置请在mybatis-config.xml中添加如下代码:
二级缓存的配置需要在Mapper.xml映射文件中,或者配置在Mapper.java接口中,和命名空间是绑定的。
2.1.1 Mapper.xml中配置二级缓存
保证二级缓存的全局配置开启,仅仅在xml文件中添加一个
默认的二级缓存会有如下效果:
属性解释:
eviction(收回策略)
flushInterval(刷新间隔):单位毫秒,默认情况不设置,即没有刷新间隔,缓存尽在调用语句是刷新。
size(引用条目):记住缓存的对象数目和运行环境的可用内存资源数目。默认1024。
readOnly(只读):可以设置为true和false。如果设置成true则会让每一个调用者返回的缓存对象都是同一个实例,因此不能修改(修改对象的值,影响别人),可是带来的好处是性能的提升。如果设置成false(可读可写)会通过序列化返回缓存对象的拷贝,相对会慢一些,但是安全,因此默认是false的。
2.1.2 Mapper接口中配置二级缓存
当只通过注解方式配置二级缓存时,需要添加如下配置:
@CacheNamespace(eviction=FifoCache.class, flushInterval=60000, size=512, readWrite=true)
public interface RoleMapper {
// 接口方法
}
注意:同时使用注解方式和XML配置文件时,如果同时配置了上述的二级缓存,就会出现异常,因为两者的命名空间相同。
解决:使用参照缓存。
其实Mybatis中很少同时使用Mapper接口配置和XML映射文件,参照缓存除了能通用其他缓存减少配置外,主要作用是解决脏读。
配置了二级缓存后,当调用所有的select查询方法时,二级缓存就开始起作用了,如果配置的是可读可写的缓存,Mybatis是通过使用SerializedCache序列化来实现可读可写缓存类,所以查询的对象需要实现Serializable接口,如果配置的是仅可读,则Mybatis通过Map来存储缓存对象,因此读出来的都是相同实例。
MyBatis默认提供的缓存实现是基于Map实现的内存缓存,己经可以满足基本的应用。但是当需要缓存大量的数据时,不能仅仅通过提高内存来使用MyBatis的二级缓存,还可以选择一些类似EhCache的缓存框架或Redis缓存数据库等工具来保存MyBatis 的二级缓存数据。接下来两节,我们会介绍两个常见的缓存框架。
EhCache是一个纯粹的Java进程内的缓存框架,具有快速、精干等特点。具体来说,EhCache主要的特性如下。
2.3.1 添加项目依赖
org.mybatis.caches
mybatis-ehcache
l.0.3
2.3.2 配置EhCache
在src/main/resources目录下添加ehcache.xml文件
有关EhCache的详细配置,参考地址:http://www.ehcache.org/ehcache.xml中的内容。
2.3.3 修改XML中的缓存配置
这样配置了cache元素的type属性为ehcache的缓存类型,再添加其他属性都不起作用了,针对缓存的配置都已经在ehcache.xml中进行。因为ehcache.xml配置文件中只能配置一个默认缓存配置(
实际生产项目使用Redis做缓存的项目不常见,所以省略,想配置可参考其他资料。
二级缓存虽然能提高应用效率,减轻数据库服务器的压力,但是如果使用不当,很容易产生脏数据。 这些脏数据会在不知不觉中影响业务逻辑, 影响应用的实效,所以我们需要了解在MyBatis缓存中脏数据是如何产生的,也要掌握避免脏数据的技巧。
MyBatis的二级缓存是和命名空间绑定的,所以通常情况下每一个Mapper映射文件都拥有自己的二级缓存,不同Mapper的二级缓存互不影响。在常见的数据库操作中,多表联合查询非常常见,由于关系型数据库的设计, 使得很多时候需要关联多个表才能获得想要的数据。在关联多表查询时肯定会将该查询放到某个命名空间下的映射文件中,这样一个多表的查询就会缓存在该命名空间的二级缓存中。涉及这些表的增、删、改操作通常不在一个映射文件中,它们的命名空间不同, 因此当有数据变化时,多表查询的缓存未必会被清空,这种情况下就会产生脏数据。
该如何避免脏数据的出现呢?这时就需要用到参照缓存了。 当某几个表可以作为一个业务整体时,通常是让几个会关联的ER表同时使用同一个二级缓存,这样就能解决脏数据问题。在上面这个例子中,将UserMapper.xrnl 中的缓存配置修改如下。
修改为参照缓存后,虽然这样可以解决脏数据的问题,但是并不是所有的关联查询都可以这么解决,如果有几十个表甚至所有表都以不同的关联关系存在于各自的映射文件中时,使用参照缓存显然没有意义。
二级缓存虽然好处很多,但并不是什么时候都可以使用。 在以下场景中,推荐使用二级缓存。
除了推荐使用的情况,如果脏读对系统没有影响,也可以考虑使用。 在无法保证数据不出现脏读的情况下, 建议在业务层使用可控制的缓存代替二级缓存。
通过本文的学习,我们知道了一级缓存和二级缓存的区别,学会了如何配置二级缓存,除了MyBatis 默认提供的缓存外,还学会了如何集成EhCache。另外,我们认识到了二级缓存可能带来的脏读问题,也学会了特定情况下解决脏读的办法。
MyBatis 的二级缓存需要在特定的场景下才会适用,在选择使用二级缓存前一定要认真考虑脏读对系统的影响。在任何情况下,都可以考虑在业务层使用可控制的缓存来代替二级缓存。
@本文内容参考《MyBatis从入门到精通》作者:刘增辉 电子工业出版社