Mybatis缓存
mybatis的缓存将相同查询条件的SQL语句执行一遍后所得到的结果存在内存或者某种缓存介质当中,当下次遇到一模一样的查询SQL时候不在执行SQL与数据库交互,而是直接从缓存中获取结果,减少服务器的压力;尤其是在查询越多、缓存命中率越高的情况下,使用缓存对性能的提高更明显。
MyBatis允许使用缓存,缓存一般放置在高速读/写的存储器上,比如服务器的内存,能够有效的提供系统性能。MyBatis分为一级缓存和二级缓存,同时也可配置关于缓存设置。
一级存储是SqlSession上的缓存,二级缓存是在SqlSessionFactory(namespace)上的缓存。默认情况下,MyBatis开启一级缓存,没有开启二级缓存。当数据量大的时候可以借助一些第三方缓存框架或Redis缓存来协助保存Mybatis的二级缓存数据

一、一级缓存
一级存储是SqlSession上的缓存,默认开启,是一种内存型缓存,不要求实体类对象实现Serializable接口。
缓存中的数据使用键值对形式存储数据。namespace+sqlid+args+offset>>> hash值作为键,查询出的结果作为值

1.1.接口
package com.augus01.mapper;
import com.augus01.pojo.Dept;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface DeptMapper {//根据部门编号,查询部门信息
@Select("select * from dept where deptno=#{deptno}")
Dept findByDeptnoDept(int deptno);
}
1.2.测试代码
第一次查询的时候,是会执行SQL语句,从数据库去提取结果的;
第二次查询相同的数据的时候,第二次就会从本sqlSession的一级缓存中去提取数据了;
public class Test1 { private static SqlSession sqlSession; @BeforeEach public void setUp(){ //创建一个对象:参照了XML 文档或上面讨论过的更特定的 mybatis-config.xml 文件的 Reader 实例 SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); InputStream resourceAsStream; try { //Resources.getResourceAsStream对于简单的只读二进制或文本数据,加载为 Stream。 resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); /* SqlSessionFactory SqlSessionFactory 有六个方法创建 SqlSession 实例。通常来说,当你选择这些方法时你需要考虑以下几点: 事务处理:需要在 session 使用事务或者使用自动提交功能(auto-commit)吗?(通常意味着很多数据库和/或 JDBC 驱动没有事务) 连接:需要依赖 MyBatis 获得来自数据源的配置吗?还是使用自己提供的配置? 执行语句:需要 MyBatis 复用预处理语句和/或批量更新语句(包括插入和删除)吗? */ SqlSessionFactory build = factoryBuilder.build(resourceAsStream); /* 默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession: 会开启一个事务(也就是不自动提交)。 将从由当前环境配置的 DataSource 实例中获取 Connection 对象。 事务隔离级别将会使用驱动或数据源的默认设置。 预处理语句不会被复用,也不会批量处理更新。 */ sqlSession = build.openSession(); } catch (IOException ioException) { ioException.printStackTrace(); } } /** * 测试一级缓存 */ @Test public void testFirstLevelCache(){ //写完接口类之后让mybatis注册这个类 sqlSession.getConfiguration().addMapper(DeptMapper.class); //获取mapper DeptMapper mapper = sqlSession.getMapper(DeptMapper.class); System.out.println("==================第一次查询===================="); Dept dept1 = mapper.findDeptByDeptNo(30); System.out.println(dept1); System.out.println("==================第二次查询===================="); Dept dept2 = mapper.findDeptByDeptNo(30); System.out.println(dept2); System.out.println("==================比较是否相等===================="); System.out.println(dept1==dept2); } @Test public void tearDown(){ sqlSession.close(); } }
上述代码执行后如下图:

二、二级缓存
二级缓存是以namespace为标记的缓存,可以是由一个SqlSessionFactory创建的SqlSession之间共享缓存数据。默认并不开启。下面的代码中创建了两个SqlSession,执行相同的SQL语句,尝试让第二个SqlSession使用第一个SqlSession查询后缓存的数据。要求实体类必须实现序列化接口

2.1.二级需要进行如下步骤完成开启。
-
全局开关:在sqlMapConfig.xml文件中的<settings>标签配置开启二级缓存
cacheEnabled的默认值就是true,所以这步的设置可以省略。
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
-
分开关:在要开启二级缓存的mapper映射文件中开启缓存:
在里面添加<cache/>标签即可
<mapper namespace="com.augus04.mapper.EmpMapper">
<cache/>
</mapper>
-
二级缓存未必完全使用内存,有可能占用硬盘存储,缓存中存储的JavaBean对象必须实现序列化接口
实现 Serializable 接口即可
@Data //@Data 注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;但是需要先导入lombok
@AllArgsConstructor //使用lombok后生成一个构造方法
@NoArgsConstructor //使用lombok后生成无参数的构造方法
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
}
2.2.案例
2.2.1.创建实体类
在pojo包下创建Emp表的实体类如下:
@Data //@Data 注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;但是需要先导入lombok
@AllArgsConstructor //使用lombok后生成一个构造方法
@NoArgsConstructor //使用lombok后生成无参数的构造方法
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;
}
2.2.2.创建接口
在mapper包创建EmpMapper里面定义接口如下:
public interface EmpMapper { /* 根据编号查询员工信息及所在的部门信息 */ List<Emp> findEmpBydeptno(@Param("deptno") int deptno); }
2.2.3.映射文件
创建映射文件EmpMapper.xml如下:
<?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"> <!-- namespace需要指定接口文件的路径 --> <mapper namespace="com.augus04.mapper.EmpMapper"> <!--在要开启二级缓存的mapper文件中开启缓存--> <cache/> <!-- 根据部门编号查询对应的员工信息 (1)当为select语句时: flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。 useCache默认为true,表示会将本条语句的结果进行二级缓存。 (2)当为insert、update、delete语句时: flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空 当为select语句的时候,如果没有去配置flushCache、useCache,那么默认是启用缓存的,如果有需要,那么就需要人工修改配置,修改结果类似下面: --> <select id="findEmpBydeptno" resultType="emp" useCache="true" flushCache="false"> SELECT * FROM emp WHERE deptno = #{deptno} </select> </mapper>
2.2.4.测试代码
创测试代码如下:
public class Test1 { private static SqlSession sqlSession1; private static SqlSession sqlSession2; @BeforeEach public void setUp(){ //创建一个对象:参照了XML 文档或上面讨论过的更特定的 mybatis-config.xml 文件的 Reader 实例 SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); InputStream resourceAsStream; try { //Resources.getResourceAsStream对于简单的只读二进制或文本数据,加载为 Stream。 resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); /* SqlSessionFactory SqlSessionFactory 有六个方法创建 SqlSession 实例。通常来说,当你选择这些方法时你需要考虑以下几点: 事务处理:需要在 session 使用事务或者使用自动提交功能(auto-commit)吗?(通常意味着很多数据库和/或 JDBC 驱动没有事务) 连接:需要依赖 MyBatis 获得来自数据源的配置吗?还是使用自己提供的配置? 执行语句:需要 MyBatis 复用预处理语句和/或批量更新语句(包括插入和删除)吗? */ SqlSessionFactory build = factoryBuilder.build(resourceAsStream); /* 默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession: 会开启一个事务(也就是不自动提交)。 将从由当前环境配置的 DataSource 实例中获取 Connection 对象。 事务隔离级别将会使用驱动或数据源的默认设置。 预处理语句不会被复用,也不会批量处理更新。 */ sqlSession1 = build.openSession(); sqlSession2 = build.openSession(); } catch (IOException ioException) { ioException.printStackTrace(); } } /** * 测试二级缓存 */ @Test public void testSecondLevelCache(){ System.out.println("==================第一次查询===================="); //获取mapper EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class); List<Emp> empBydeptno1 = mapper1.findEmpBydeptno(30); System.out.println(empBydeptno1); // SqlSession提交之后,才会将查询的结果放入二级缓存 sqlSession1.commit(); System.out.println("************************************"); System.out.println("==================第二次查询===================="); //获取mapper EmpMapper mapper2 = sqlSession1.getMapper(EmpMapper.class); List<Emp> empBydeptno2 = mapper2.findEmpBydeptno(30); System.out.println(empBydeptno2); } @Test public void tearDown(){ sqlSession1.close(); sqlSession2.close(); } }
注意其中的commit(),执行该命令后才会将该SqlSession的查询结果从一级缓存中放入二级缓存,供其他SqlSession使用。另外执行SqlSession的close()也会将该SqlSession的查询结果从一级缓存中放入二级缓存。两种方式区别在当前SqlSession是否关闭了。执行后截图如下,第二次调用,就使用之前二级缓存中的结果

2.2.5.注意事项:
- MyBatis的二级缓存的缓存介质有多种多样,而并不一定是在内存中,所以需要对JavaBean对象实现序列化接口。
- 二级缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响
- 加入Cache元素后,会对相应命名空间所有的select元素查询结果进行缓存,而其中的insert、update、delete在操作是会清空整个namespace的缓存。
- cache 有一些可选的属性 type, eviction, flushInterval, size, readOnly, blocking。
<cache type="" readOnly="" eviction=""flushInterval=""size=""blocking=""/>
|
属性 |
含义 |
默认值 |
|
type |
自定义缓存类,要求实现org.apache.ibatis.cache.Cache接口 |
null |
|
readOnly |
是否只读 true:给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。 false:会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全 |
false |
|
eviction |
缓存策略 LRU(默认) – 最近最少使用:移除最长时间不被使用的对象。 FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。 WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。 |
LRU |
|
flushInterval |
刷新间隔,毫秒为单位。默认为null,也就是没有刷新间隔,只有执行update、insert、delete语句才会刷新 |
null |
|
size |
缓存对象个数 |
1024 |
|
blocking |
是否使用阻塞性缓存BlockingCache true:在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,保证只有一个线程到数据库中查找指定key对应的数据 false:不使用阻塞性缓存,性能更好 |
false |
如果在加入Cache元素的前提下让个别select 元素不使用缓存,可以使用useCache属性,设置为false。useCache控制当前sql语句是否启用缓存 flushCache控制当前sql执行一次后是否刷新缓存

再次执行测试代码,发现两次查询直接已经不在使用二级混存了,如下:

三、三级缓存
分布式缓存框架:我们系统为了提高系统并发 和性能,一般对系统进行分布式部署(集群部署方式)不适用分布缓存, 缓存的数据在各个服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理.ehcache,redis ,memcache缓存框架。
Ehcache:是一种广泛使用的开源java分布式缓存。主要面向通用缓存,javaEE 和 轻量级容器。它具有内存和磁盘存储功能。被用于大型复杂分布式web application的
注意:这里的三方缓存是作为二级缓存使用的
3.1.导入依赖的jar文件
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
3.2.去各自的sql映射文件里,开启二级缓存,并把缓存类型指定为EhcacheCache
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
3.3.在资源目录resources下放置一个缓存配置文件,文件名为: ehcache.xml 内容如下
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true"> <!-- Cache配置 name:Cache的唯一标识 maxElementsInMemory:内存中最大缓存对象数。 maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大。 eternal:Element是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中。 timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 --> <diskStore path="D:\augus\ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>

浙公网安备 33010602011771号