Mybatis 的缓存

一、一级缓存

1.1 简介

        一级缓存是 SqlSession 级别的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。且一级缓存是默认开启的。

1.2 在相同 SqlSession 获取数据

public interface CacheMapper {

    /**
     * 根据 eid 查询用户信息
     * @param id
     * @return
     */
    Emp getEmpByEid(@Param("eid") int eid);

}
<?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.mybatis.mapper.CacheMapper">

    <select id="getEmpByEid" resultType="Emp">
        select * from t_emp  where eid = #{eid}
    </select>

</mapper>

        测试代码如下:

@Test
public  void testGetEmpByEid() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
			SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
	SqlSession sqlSession = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);

	Emp emp = cacheMapper.getEmpByEid(2);
	System.out.println(emp);

	Emp emp2 = cacheMapper.getEmpByEid(2);
	System.out.println(emp2);
}

        可以看到,当执行第二次查询时,并没有查询数据库的日志输出,它获取的数据是从缓存里面获取的。

1.3 在不同 SqlSession 获取数据

@Test
public  void testGetEmpByEid() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
			SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);

	SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper = sqlSession1.getMapper(CacheMapper.class);
	Emp emp = cacheMapper.getEmpByEid(2);
	System.out.println(emp);

	SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper2 = sqlSession2.getMapper(CacheMapper.class);
	Emp emp2 = cacheMapper2.getEmpByEid(2);
	System.out.println(emp2);
}

        可以看到,sql 语句有两个输出,足以证明一级缓存的范围就是 sqlSession 。

1.4 缓存失效情况

        1、不同的 SqlSession 对应不同的一级缓存。

        2、 同一个 SqlSession 但是查询条件不同。

        3、同一个 SqlSession 两次查询期间执行了任何一次增删改操作。

        4、同一个 SqlSession 两次查询期间手动清空了缓存。

@Test
public  void testGetEmpByEid() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
			SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
	SqlSession sqlSession = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);

	Emp emp = cacheMapper.getEmpByEid(2);
	System.out.println(emp);
	// 手动清空缓存
	sqlSession.clearCache();

	Emp emp2 = cacheMapper.getEmpByEid(2);
	System.out.println(emp2);
}

二、二级缓存

2.1 简介

        二级缓存是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。

2.2 开启条件

        1、在核心配置文件中,设置全局配置属性 cacheEnabled="true",默认为 true,不需要设置。

<settings>
	<!-- 开启二级缓存,默认为 true,不需要配置-->
	<setting name="cacheEnabled" value="true"/>
</settings>

        2、在映射文件中设置标签 <cache />

<?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.mybatis.mapper.CacheMapper">
    
    <cache />

    <select id="getEmpByEid" resultType="Emp">
        select * from t_emp  where eid = #{eid}
    </select>
    
</mapper>

        3、二级缓存必须在 SqlSession 关闭或提交之后有效

@Test
public  void testGetEmpByEidTwo() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
			SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);

	SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper = sqlSession1.getMapper(CacheMapper.class);
	Emp emp = cacheMapper.getEmpByEid(2);
	System.out.println(emp);
	// 关闭
	sqlSession1.close();

	SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper2 = sqlSession2.getMapper(CacheMapper.class);
	Emp emp2 = cacheMapper2.getEmpByEid(2);
	System.out.println(emp2);
}

        4、查询的数据所转换的实体类类型必须实现序列化的接口

public class Emp implements Serializable {
    private Integer eid;

    private String empName;

    private Integer age;

    private String sex;

    private String email;

    private Dept dept;

    // setter、getter、toString()
}

2.3 缓存失效情况

        当两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效。

2.4 验证

@Test
public  void testGetEmpByEidTwo() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
			SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);

	SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper = sqlSession1.getMapper(CacheMapper.class);
	Emp emp = cacheMapper.getEmpByEid(2);
	System.out.println(emp);
	// 关闭
	sqlSession1.close();

	SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper2 = sqlSession2.getMapper(CacheMapper.class);
	Emp emp2 = cacheMapper2.getEmpByEid(2);
	System.out.println(emp2);
}

        从输出结果可以看到,第二次的查询并没有任何的 sql 日志输出,查询到的数据是从二级缓存里面获取的。 

2.5 相关配置

        在 mapper 配置文件中添加的 cache 标签可以设置一些属性

2.5.1 eviction 缓存回收策略

        1、LRU(Least Recently Used)最近最少使用的:移除最长时间不被使用的对象,默认。

        2、FIFO(First in First out)先进先出:按对象进入缓存的顺序来移除它们。

        3、SOFT 软引用:移除基于垃圾回收器状态和软引用规则的对象。

        4、WEAK 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

2.5.2 flushInterval

        刷新间隔,单位毫秒。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用增删改语句时刷新。

2.5.3 size

        引用数目,正整数。代表缓存最多可以存储多少个对象,太大容易导致内存溢出

2.5.4 readOnly 只读

        1、true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。

        2、false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false

三、缓存查询的顺序

        1、先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。

        2、如果二级缓存没有命中,再查询一级缓存。

        3、如果一级缓存也没有命中,则查询数据库。

        4、SqlSession 关闭之后,一级缓存中的数据会写入二级缓存。

四、整合 EHCache

4.1 添加依赖

<!-- Mybatis EHCache整合包-->
<dependency>
	<groupId>org.mybatis.caches</groupId>
	<artifactId>mybatis-ehcache</artifactId>
	<version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.2.3</version>
</dependency>

4.2 创建 ehcache 配置文件

        在 resource 目录下,创建 ehcache.xml 文件,内容如下:

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="E:\ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

4.3 设置二级缓存的类型

        mapper 文件中设置二级缓存的类型为 ehcahce,如下:

<?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.mybatis.mapper.CacheMapper">

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

    <select id="getEmpByEid" resultType="Emp">
        select * from t_emp  where eid = #{eid}
    </select>

</mapper>

4.4 加入 logback 日志

        存在 SLF4J 时,作为简易日志的 log4j 将失效,此时我们需要借助 SLF4J 的具体实现 logback 来打印日志。在 resources 目录下创建 logback 的配置文件 logback.xml,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
    <!-- 日志输出的格式 -->
    <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
    <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
    [%msg]%n</pattern>
    </encoder>
    </appender>
    <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="com.crowd.mapper" level="DEBUG"/>
</configuration>

4.5 测试

@Test
public  void testGetEmpByEidTwo() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new
			SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);

	SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper = sqlSession1.getMapper(CacheMapper.class);
	Emp emp = cacheMapper.getEmpByEid(2);
	System.out.println(emp);
	// 关闭
	sqlSession1.close();

	SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
	CacheMapper cacheMapper2 = sqlSession2.getMapper(CacheMapper.class);
	Emp emp2 = cacheMapper2.getEmpByEid(2);
	System.out.println(emp2);
}

        可以看到,第一次查询的是数据库,第二次查询查询的就是 ehcachce 了。 

4.6 ehcache 配置文件说明

属性名
作用
maxElementsInMemory
在内存中缓存的  element  的最大数目
maxElementsOnDisk
在磁盘上缓存的  element  的最大数目,若是  表示无穷大
eternal
设定缓存的 elements 是否永远不过期。 如果为 true,则缓存的数据始终有效, 如果为 false 那么还要根据timeToIdleSeconds、timeToLiveSeconds 判断
overflowToDisk
设定当内存缓存溢出的时候是否将过期的 element 缓存到磁盘上
timeToIdleSeconds
当缓存在 EhCache 中的数据前后两次访问的时间超过timeToIdleSeconds 的属性取值时, 这些数据便会删除,默认值是 0,也就是可闲置时间无穷大
timeToLiveSeconds
缓存 element 的有效生命期,默认是 0.,也就是 element 存活时间无穷大。
diskSpoolBufferSizeMB
DiskStore(磁盘缓存)的缓存区大小。默认是 30MB。每个Cache 都应该有自己的一个缓冲区
diskPersistent
在VM重启的时候是否启用磁盘保存 EhCache 中的数据,默认是false。
diskExpiryThreadIntervalSeconds
磁盘缓存的清理线程运行间隔,默认是 120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy
当内存缓存达到最大,有新的 element 加入的时候, 移除缓存中 element 的策略。 默认是 LRU(最近最少使用),可选的有 LFU(最不常使用)和 FIFO(先进先出)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的小三菊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值