Mybaits(13)缓存
一、概述
MyBatis像大多数持久层框架一样,也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。
MyBatis分为一级缓存和二级缓存,同时也可以配置关于缓存的设置。
二、一级缓存
1.介绍
一级缓存是在SqlSession上的缓存,只要 SqlSession 没有 flush 或 close,它就存在。默认情况下,也就是没有任何配置的情况下,MyBatis系统会开启一级缓存,也就是对于SqlSession层面的缓存,这个缓存不需要POJO对象序列化(实现java.io.Serializable接口)。
2.测试,证明一级缓存的存在
(1)编写测试类
@Test public void testCache() { // 5.创建Dao的代理对象 roleDao = session.getMapper(IRoleDao.class); logger.info("第一次获取..."); Role role = roleDao.getRole(1L); logger.info("第二次获取..."); Role role1 = roleDao.getRole(1L); }
(2)查看日志
(3)分析
虽然代码对同一对象进行了两次获取,但是实际上只有一条SQL,其原因是代码使用了同一个SqlSession对象获取数据,当一个SqlSession第一次通过Sql和参数获取对象后,它就将其缓存起来,如果下次Sql和参数都发生变化,并且缓存没有超时或者声明需要刷新时,它就会从缓存中获取数据,而不是通过Sql获取了。
当一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等会清空一级缓存。如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
(4)一级缓存清空测试
清空缓存
@Test public void testClearCache() { // 5.创建Dao的代理对象 roleDao = session.getMapper(IRoleDao.class); logger.info("第一次获取..."); Role role = roleDao.getRole(1L); //此方法也可以清空缓存 session.clearCache(); logger.info("第二次获取..."); Role role1 = roleDao.getRole(1L); logger.info(role==role1); }
从日志可以看出sql执行了两次,最后执行结果为false,缓存被清空。对于不同的SqlSession对象是不能共享的,为了使SqlSession对象之间能共享相同的缓存,有时需要开启二级缓存,开启二级缓存很简单,只需要在映射文件上加入代码:<cache/>,这个时候MyBatis会序列化和反序列对应的实体类(POJO),也就要求POJO是一个可序列化的对象,那么必须实现java.io.Serializable接口。
二、二级缓存
1、概念
指的是MyBatis中的SqlSessionFactory对象的缓存。由于同一个SqlSessionFactory对象的创建的SQLSession共享其内存。
2、二级缓存测试
(1)二级缓存开与关
<settings> <setting name="lazyLoadingEnabled" value="true" /> <setting name="aggressiveLazyLoading" value="false" /> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> </settings>
(2)配置相关的 Mapper 映射文件
<?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="com.xhbjava.dao.IUserDao"> <cache></cache> <resultMap type="com.xhbjava.domain.User" id="userMapper"> <id column="id" property="id" /> <result column="user_name" property="userName" /> <result column="real_name" property="realName" /> <result column="sex" property="sex" typeHandler="com.xhbjava.typeHandler.SexTypeHandler" /> <result column="mobile" property="moble" /> <result column="email" property="email" /> <result column="position" property="position" /> <result column="note" property="note" /> <collection property="roleList" column="id" fetchType="lazy" select="com.xhbjava.dao.IRoleDao.findRoleByUserId" /> </resultMap> <select id="getUser" parameterType="long" resultMap="userMapper"> select id, user_name, real_name, sex, moble, email, note from t_user where id =#{id} </select> <select id="findUserByRoleId" parameterType="long" resultMap="userMapper"> select u.id, u.user_name, u.real_name, u.sex, u.moble, u.email, u.note from t_user u , t_user_role ur where u.id = ur.user_id and ur.role_id =#{roleId} </select> </mapper>
(3)配置 statement 上面的 useCache 属性
<select id="getUser" parameterType="long" resultMap="userMapper" useCache="true"> select id, user_name, real_name, sex, moble, email, note from t_user where id =#{id} </select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
(4)编写测试类进行测试
@Test
public void testSecondLevelCache() {
// 5.创建Dao的代理对象
session = factory.openSession(true);
userDao = session.getMapper(IUserDao.class);
logger.info("第一次获取...");
User user = userDao.getUser(1L);
//一级缓存消失
session.close();
logger.info("第二次获取...");
// 5.创建Dao的代理对象
session11 = factory.openSession(true);
userDao1 = session11.getMapper(IUserDao.class);
User user1 = userDao1.getUser(1L);
logger.info(user==user1);
}
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。
注意:使用二级缓存,缓存的类一定要实现 java.io.Serializable 接口,这样可以使用序列化方式来保存对象。
三、缓存配置项、自定义和引用
1.cache元素配置项:
属性 | 说明 | 取值 | 备注 |
blocking | 是否使用阻塞性缓存,在读写时会加入JNI的锁进行操作 | true|false,默认false | 保证读写安全性,但加锁后性能不佳 |
readOnly | 缓存内容是否只读 | true|false,默认false | 如果是只读,不会因为多个线程读写造成不一致 |
eviction |
缓存策略: LRU最近最少使用:移除最长时间不被使用的对象 FIFO先进先出:按照对象进入缓存的顺序移除 SOFT软引用:移除基于垃圾回收器状态和软引用规则对象 WEAK弱引用:积极移除基于垃圾收集器状态和弱引用规则对象 |
默认false | |
flushInterval |
这是一个整数,以毫秒为单位,比如1分钟刷新一次,则配置60000. 默认为null,也就是没有刷新时间,只有执行update,inset和delete 时才会刷新。 |
正整数 |
超过设置的整数后缓存消失,不再读取缓存,而是执行SQL 取回数据。 |
type | 自定义缓存类。要求实现org.apache.ibatis.cache.Cache | 用于自定义缓存类 | |
size | 缓存对象个数 | 正整数。默认是1024 |
2.自定义缓存类
在实际工作中,我们可以使用Redis,MongoDB或者其他常用的缓存,假设我们在存在Redis的一个缓存实现类RedisCache,我们可以这样配置:
<cache type="XXX.XX.RedisCache">
<property name="host" value="localhost" />
</cache>
这样配置后,MyBatis就会启用缓存,同时调用setHost(String host)方法,去设置配置的内容。
上面的配置时通用的,对于一些语句也需要自定义,比如一些查询不想让它们进行缓存,我们可以如下配置:
<select ... flushCache="false" userCache="true">
<insert ... flushCache="true" >
<update... flushCache="true" >
<delete ... flushCache="true" >
以上是默认配置,我们可以根据需要去修改。flashCache代表是否刷新缓存,对于select、insert、update和delete都是有效的。useCache是select特有的,代表是否启用缓存。
这里都是在一个映射配置文件中配置的,比如IUserDao.xml,其他的映射配置文件不能使用,如果其他映射配置文件需要使用同样的配置,则可以引用缓存的配置:
<cache-ref namespace="XXX.XX.IUserDao"/>这样就可以引用对应映射文件的cache元素的配置了。