文章目录
1. 缓存介绍
- mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
2. 一级缓存
2.1 引入原因&解决方案
- 引入缓存原因
每当使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。 在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。 - 解决方法:MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了.
==一级缓存是SqlSession级别的缓存。==在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存是存在于SqlSession对象中的一个HashMap。其中Key:sql查询的唯一标识:sqlStatement的Id(namespace)+sql参数+mybatis提供的随机数
Value:sql的结果集
Statement id = findOrderById sql映射的namespace=cn.com.hxzy
Select * from orders where id =1 -> key: cn.com.hxzy.findOrderById+1+随机数 value:id=1的订单信息
Select * form orders where id= 2 -> key: cn.com.hxzy.findOrderById+2+随机数 value:id=2的订单信息
2.2 执行流程
2.3 缓存状态介绍
① MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor(执行者)对象,Executor(执行者)对象中持有一个新的Perpetual Cache(永久缓存)对象;当会话结束时,SqlSession对象及其内部的Executor对象还有Perpetual Cache(永久缓存)对象也一并释放掉。
② 如果SqlSession调用了close()方法,会释放掉一级缓存Perpetual Cache对象,一级缓存将不可用。
③ 如果SqlSession调用了clearCache(),会清空Perpetual Cache对象中的数据,但是该对象仍可使用。
④ SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空Perpetual Cache对象的数据,但是该对象可以继续使用。
2.4 框架中缓存接口和默认实现(了解)
- Mybatis缓存实现接口
- Mybatis一级缓存的默认实现类
2.5 一级缓存测试
-
测试1:测试一级缓存的存在
/** * 测试一级缓存 * @throws IOException */ @Test public void method01() throws IOException { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper1 userMapper1 = sqlSession.getMapper(UserMapper1.class); User user = userMapper1.findUserById(1); // Preparing: SELECT * FROM USER WHERE id = ? System.out.println(user); System.out.println("---------------------------"); System.out.println(userMapper1.findUserById(1)); sqlSession.close(); }
- 测试2:测试一级缓存的自动清空
/**
* 测试一级缓存
* @throws IOException
*/
@Test
public void method01() throws IOException {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper1 userMapper1 = sqlSession.getMapper(UserMapper1.class);
User user = userMapper1.findUserById(1); // Preparing: SELECT * FROM USER WHERE id = ?
System.out.println(user);
System.out.println("---------------------------");
user.setAddress("新的地址");
userMapper1.updateUser(user); //更新用户信息
sqlSession.commit();//sqlSession 提交
// sqlSession.clearCache();// 强制清空缓存数据
System.out.println("---------------------------");
System.out.println(userMapper1.findUserById(1));
sqlSession.close();
}
3. 二级缓存
- 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
3.1 使用步骤
Mybatis中默认关闭二级缓存,使用前必须先打开
-
步骤1:在核心配置文件SqlSessionFactory.xml中加入以下内容(开启二级缓存总开关):
在settings标签中添加以下内容:<!-- 开启二级缓存总开关 --> <setting name="cacheEnabled" value="true"/>
-
步骤2:在UserMapper映射文件中,加入以下内容,开启二级缓存:
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache --> <cache></cache>
-
步骤3:使用二级缓存存储的实体类需要实现序列化接口 :
public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; }
-
3.2 测试二级缓存
-
测试二级缓存的存在
/** * 测试二级缓存 * @throws IOException */ @Test public void method03() throws IOException { SqlSession sqlSession1 = sqlSessionFactory.openSession(); UserMapper1 userMapper1 = sqlSession1.getMapper(UserMapper1.class); User user1 = userMapper1.findUserById(1); System.out.println(user1); sqlSession1.close(); System.out.println("------------------------------------------"); SqlSession sqlSession2 = sqlSessionFactory.openSession(); UserMapper1 userMapper2 = sqlSession2.getMapper(UserMapper1.class); User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }
-
测试二级缓存的自动刷新
@Test public void method02() throws IOException { SqlSession sqlSession1 = sqlSessionFactory.openSession(); UserMapper1 userMapper1 = sqlSession1.getMapper(UserMapper1.class); User user1 = userMapper1.findUserById(1); System.out.println(user1); sqlSession1.close(); System.out.println("------------------------------------------"); SqlSession sqlSession3 = sqlSessionFactory.openSession(); UserMapper1 userMapper3 = sqlSession3.getMapper(UserMapper1.class); user1.setAddress("新的地址"); userMapper3.updateUser(user1); //更新用户信息 sqlSession3.commit();//sqlSession 提交 System.out.println("------------------------------------------"); SqlSession sqlSession2 = sqlSessionFactory.openSession(); UserMapper1 userMapper2 = sqlSession2.getMapper(UserMapper1.class); User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }
3.3 禁用二级缓存
- 该statement中设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
3.4 刷新二级缓存
- 该statement中设置flushCache=true可以刷新当前的二级缓存,
默认情况下
如果是select语句,那么flushCache是false。
如果是insert、update、delete语句,那么flushCache是true。
如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
4. MyBatis整合分布式缓存ehcache
4.1 ehcache
- Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
- Ehcache最初是由Greg Luck于2003年开始开发。2009年,该项目被Terracotta购买。软件仍然是开源,但一些新的主要功能(例如,快速可重启性之间的一致性的)只能在商业产品中使用,例如Enterprise EHCache and BigMemory。维基媒体Foundationannounced目前使用的就是Ehcache技术。
- Mybaits侧重于sql语句的映射实现,对于缓存实现并不好,但Mybatis提供了缓存接口(Catch),可以通过该接口整合第三方缓存实现,如常用的分布式缓存技术 ehcache
4.2 整合方法:
- 步骤1:添加ehcache的依赖
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.0</version>
</dependency>
<!-- mybatis第三方缓存ehcache 插件 end-->
-
步骤2:添加ehcache的配置文件
<?xml version="1.0" encoding="UTF-8"?><ehcache> <diskStore path="d:\\tmpdir" /> <defaultCache maxElementsInMemory="10" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /></ehcache> <cache name="com.hxzy.mapper.UserMapper1" 【name】属性的值与mapper中的namespace相同,可设置该namespace下的mapper都使用该缓存策略。 maxElementsInMemory="150" eternal="false" timeToLiveSeconds="36000" timeToIdleSeconds="3600" overflowToDisk="true"/> </ehcache>
缓存配置
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
- 步骤3: 在Mapper映射文件中指定使用ehcache缓存技术
4.3 使用场景
对于要求访问响应数据高,数据本身比较稳定(很少被修改)且安全性要求低,这样的数据适合采用二级缓存处理
4.4 局限性:
Mybatis二级缓存对细粒度的数据级别的缓存实现不好。
场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的数据操作单独放到另一个namespace的mapper中。