目录
第一节 Mybatis的缓存
1.1 Mybatis的缓存理解
- Mybatis的缓存与hibernate的缓存类似,包括一级缓存和二级缓存,一级缓存是默认使用的,二级缓存需要手动开启。
- 一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成的一个唯一值。一级缓存中的value,就是查询出的结果对象。
- 二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。
在sqlSession1中取一个数据,在sqlSession2中也能取到同一个数据而不需要使用SQL,此时就要使用二级缓存了。
二级缓存是所有sqlSession共享的
1.2 一级缓存
原理
- 第一次查询id为1的用户会执行sql,会默认的进行一级缓存将数据存入一级缓存的Map中去,当进行修改、添加、删除用户等操作提交后,会清空一级缓存,再次查询id为1的用户还需要再次执行sql
使用与测试
- 之前的save与findUserById方法
- 注意要配置使用的类实现序列化接口(就是一个可序列化的标志),否则报错
@Test
public void test1() throws IOException {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.通过SqlSessionFactory创建SqlSession
SqlSession sqlSession = sessionFactory.openSession();
//默认情况下的一级缓存是开启的
//4.通过会话获取dao接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//第二次不会执行sql
User user2 = userMapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
- 当涉及保存、更新、删除等操作会自动清除一级缓存
@Test
public void test2() throws IOException {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.通过SqlSessionFactory创建SqlSession
SqlSession sqlSession = sessionFactory.openSession();
//默认情况下的一级缓存是开启的
//保存、删除、更新后,一级缓存会自动清空,下次查询会再次执行sql
//4.通过会话获取dao接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//保存一个新用户
userMapper.save(new User("shu1","男",new Date(),"北京市"));
//若要保存到数据库记得提交事务,否则不影响数据库,这里只做演示一级缓存可不提交
sqlSession.commit();
//此时第二次会执行sql
User user2 = userMapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
1.3 二级缓存
原理
使用与测试
- 开启二级缓存总开关(在全局配置文件中配置)(更多settings设置在day01下中)
- 在UserMapper.xml中配置二级缓存
- 测试
- 注意要配置使用的类实现序列化接口(就是一个可序列化的标志),否则报错
//二级缓存
@Test
public void test3() throws IOException {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.通过SqlSessionFactory创建SqlSession
SqlSession sqlSession1 = sessionFactory.openSession();
SqlSession sqlSession2 = sessionFactory.openSession();
//默认情况下的一级缓存是开启的
//保存、删除、更新后,一级缓存会自动清空,下次查询会再次执行sql
//4.通过会话获取dao接口
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
//session关闭了,数据才会写入二级缓存
sqlSession1.close();
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
- 如果执行了插入、更新、删除等操作,二级缓存会清除
@Test
public void test4() throws IOException {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.通过SqlSessionFactory创建SqlSession
SqlSession sqlSession1 = sessionFactory.openSession();
SqlSession sqlSession2 = sessionFactory.openSession();
SqlSession sqlSession3 = sessionFactory.openSession();
//默认情况下的一级缓存是开启的
//保存、删除、更新后,一级缓存会自动清空,下次查询会再次执行sql
//4.通过会话获取dao接口
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close();
//保存用户,二级缓存也会清空
userMapper3.save(new User("shu2","男",new Date(),"上海市"));
sqlSession3.commit();
sqlSession3.close();
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
禁用指定方法的二级缓存
- 禁用了指定方法的二级缓存,该方法的二级缓存不生效,其它方法不影响
刷新缓存
- 上面禁用了指定方法的二级缓存,这里要使用该方法体现二级缓存,记得解除禁用,否则无刷新缓存的效果
总结
一级缓存与二级缓存的共同点:当执行了插入、更新、删除等操作,缓存会清除。
1.4 整合ehcache
- 问:为什么不使用Mybatis自带的缓存,而要整合ehcache缓存呢?
- 答:Mybatis本身是一个持久层框架,它不是专门的缓存框架,所以它对缓存的实现不够好,不能支持分布式,而Ehcache是一个分布式的缓存框架。
什么是分布式
- 系统为了提高性能,通常会对系统采用分布式部署(集群部署方式)
整合思路
- 缓存有一个规范化的接口Cache(谁要使用它,就实现它,并写上对应的方法即可),它的默认实现是mybatis的PerpetualCache。如果想整合mybatis的二级缓存,那么实现Cache接口即可。
- Mybatis自带的实现cache
整合步骤
第一步:导入jar包
- 可以看到ehcache实现的cache
第二步:在映射文件中配置cache标签
<!--配置缓存
type不写,默认使用的就是mybatis自带的缓存技术,perpetualCache永久缓存
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
第三步:在src下添加ehcache的配置文件
-
拷贝到src目录下,并改名为ehcache.xml(删除注释后)
-
maxElementsInMemory:设置基于内存的缓存中可存放的对象最大数目。
-
eternal:设置对象是否为永久的,true表示永不过期,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
-
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位,超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。 (单位为秒)
-
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中。该属性值必须大于或等于 timeToIdleSeconds 属性值 (单位为秒)
-
overflowToDisk:设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
-
diskPersistent:当jvm结束时是否持久化对象(存储到硬盘),true false可选,默认是false
-
diskExpiryThreadIntervalSeconds:指定专门用于清除过期对象的监听线程的轮询时间。(单位为秒)
-
memoryStoreEvictionPolicy:当内存缓存达到最大,有新的element加入的时候, 移除缓存中element向磁盘缓存时的策略。默认是LRU(最近最少使用)可选的有LFU(最不常使用、最少使用)和FIFO(先进先出)
-
LRU(Least Recently Used):缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-
LFU(Less Frequently Used ):缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。
第四步:测试
- 使用之前的测试二级缓存
//二级缓存
@Test
public void test3() throws IOException {
//1.读取配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.通过SqlSessionFactory创建SqlSession
SqlSession sqlSession1 = sessionFactory.openSession();
SqlSession sqlSession2 = sessionFactory.openSession();
//默认情况下的一级缓存是开启的
//保存、删除、更新后,一级缓存会自动清空,下次查询会再次执行sql
//4.通过会话获取dao接口
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
//session关闭了,数据才会写入二级缓存
sqlSession1.close();
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
- 执行一次sql,第二次从二级缓存中取
1.5 二级缓存的使用场景
- 对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。 - 例如:设置缓存刷新间隔为60分钟(每隔60分钟清空一次缓存)
1.6 二级缓存的局限性
-
Mybatis二级缓存对细粒度的数据,缓存实现不好。
-
例如:对商品信息进行缓存,由于商品信息查询访问量大,用户每次查询都要是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其它商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息更新,所有商品的信息缓存数据都会清空。
-
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
-
比如可以对经常变化的数据操作单独放到另一个namespace的mapper中。