Mybatis二级缓存配置中readOnly属性的理解

前提知识

一级缓存默认开启, 二级缓存默认是不开启的.

如果两次 ( 或者多次 ) 执行的查询 SQL 相同 , 每次都要从磁盘中去读数据 , 速度是受磁盘 IO 的限制 .
如果加入缓存, 相同的 sql 只有第一次需要从磁盘中查询数据 , 并且把数据缓存到内存中 , 那么从第二次查询开始, 程序就可以直接从内存中快速的拿到数据.

一, 二级的作用范围

一级缓存

这里先介绍一下一级缓存: 一级缓存 默认开启 , 作用范围是当前的 sqlSession , 又被称为 本地缓存
举例: 
MybatisUtil类
package com.softeem.wolf.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

/**
 * Created by 苍狼
 * Time on 2021-12-20
 */
public class MybatisUtil {

    /**
     * 让sqlSessionFactory全局唯一, 可以将它变成static修饰的, 这样它就不属于某个对象, 而属于MybatisUtil类了
     */
    private static SqlSessionFactory sqlSessionFactory = null;

    static {
        try{
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            //一旦成功创建了SqlSessionFactory对象, 代表Mybatis的环境已经被初始化了
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e){
            e.printStackTrace();
            //抛出异常, 让调用方知道初始化失败了
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 创建连接
     * @return
     */
    public static SqlSession createSession(){
        return sqlSessionFactory.openSession();
    }

    public static void closeSession(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}

测试类

@Test
    public void test01(){
        SqlSession sqlSession = null;
        try{
            sqlSession = MybatisUtil.createSession();
            StudentDao mapper = sqlSession.getMapper(StudentDao.class);
            Student student = mapper.selectStudentById(1);
            Student student1 = mapper.selectStudentById(1);
            System.out.println(student);
            System.out.println(student1);
            System.out.println(student.hashCode());
            System.out.println(student1.hashCode());
            System.out.println(student==student1);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession);
        }
    }

运行结果

从这里可以看出一级缓存默认是开启的, 不需要再配置文件中开启, 还有就是一级缓存是作用在SqlSession类上.

 测试类:

    @Test
    public void test05(){
        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        try{
            sqlSession1 = MybatisUtil.createSession();
            sqlSession2 = MybatisUtil.createSession();
            StudentDao mapper1 = sqlSession1.getMapper(StudentDao.class);
            StudentDao mapper2 = sqlSession2.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            Student student2 = mapper2.selectStudentById(1);
            System.out.println(student1.hashCode());
            System.out.println(student2.hashCode());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
            MybatisUtil.closeSession(sqlSession2);
        }
    }

运行结果:

二级缓存

1xml 配置中开启 二级缓存

<settings>
    <!-- 开启二级缓存(默认是开的,这里写出来是为了方便代码维护) -->
    <setting name="cacheEnabled" value="true" />
</settings>

2去mapper映射文件中使用二级缓存

<!-- 开启本mapper所在namespace的二级缓存 -->
<cache />

<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="false"/>
测试类:
@Test
    public void test06(){
        SqlSession sqlSession1 = null;
        try{
            sqlSession1 = MybatisUtil.createSession();
            StudentDao mapper1 = sqlSession1.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            Student student2 = mapper1.selectStudentById(1);
            System.out.println(student1.hashCode());
            System.out.println(student2.hashCode());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
        }


        SqlSession sqlSession2 = null;
        try{
            sqlSession2 = MybatisUtil.createSession();
            StudentDao mapper1 = sqlSession2.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            Student student2 = mapper1.selectStudentById(1);
            System.out.println(student1.hashCode());
            System.out.println(student2.hashCode());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
        }
    }

运行结果:

总结: 

在开二级缓存之后 , 日志会多一个关于某个 namespace 的二级缓存命中率的记录
另外 , 如果某个 sql 的执行 , 既可以命中一级缓存 , 也可以命中二级缓存 , 优先使用 一级缓存, 一级缓存拿到的对象就是原本缓存的对象
<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="false"/>
1. eviction:指定内存的淘汰/回收策略
  • LRU(Least Recently Used 默认) – 最近最少使用:移除最长时间不被使用的对象。比较符合我们的预期的 ,因为缓存就是希望保留热点数据,淘汰非热点数据
  • FIFO(First In First Out) – 先进先出:类似于队列,按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。比软引用更弱
2. flushInterval:刷新间隔,单位是毫秒.默认是不刷新
3. size:缓存的数量的上限,默认值是1024.超过就会触发淘汰策略
4. readOnly:只读,默认是false(可读可写)   
  • -false的情况,缓存的对象必须要实现序列化接口,这是因为拿到的缓存对象不是原本缓存的对 象 ,是因为在false的情况下,Mybatis对缓存的对象进行了拷贝,拷贝需要序列化,这样的情况,因为涉及到序列化的操作,所以效率会更慢一些,但是更安全.       
  • -true代表只读,就是拿到的缓存对象就是原本缓存的那个对象

清空缓存的方式

  • 1、 session.clearCache( ) ;
  • 2、 execute update(增删改) ;
  • 3、 session.close( );
  • 4、 xml配置 flushCache=“true” ;
  • 5、 rollback;
  • 6、 commit。
这里主要讲一下readOnly这个属性

true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此返回的这些缓存对象自己没事不要去修改。一旦修改,就会影响下一个相同查询条件的查询结果。这提供了很重要的性能优势。

false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这些对象可以随意修改,不会影响后续相同查询条件的结果。这会慢一些,但是安全,因此默认是false。

测试一:

<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>

测试类:
@Test
    public void test06(){
        SqlSession sqlSession1 = null;
        try{
            sqlSession1 = MybatisUtil.createSession();
            StudentDao mapper1 = sqlSession1.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            Student student2 = mapper1.selectStudentById(1);
            System.out.println(student1.hashCode());
            System.out.println(student2.hashCode());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
        }

        SqlSession sqlSession2 = null;
        try{
            sqlSession2 = MybatisUtil.createSession();
            StudentDao mapper1 = sqlSession2.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            System.out.println(student1);
            student1.setStudentName("aaaa");
            System.out.println(student1);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
        }

        try{
            StudentDao mapper1 = sqlSession2.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            System.out.println(student1);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
        }
    }

运行结果:

测试二:

<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="false"/>

测试类:
@Test
    public void test06(){
        SqlSession sqlSession1 = null;
        try{
            sqlSession1 = MybatisUtil.createSession();
            StudentDao mapper1 = sqlSession1.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            Student student2 = mapper1.selectStudentById(1);
            System.out.println(student1.hashCode());
            System.out.println(student2.hashCode());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
        }

        SqlSession sqlSession2 = null;
        try{
            sqlSession2 = MybatisUtil.createSession();
            StudentDao mapper1 = sqlSession2.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            System.out.println(student1);
            student1.setStudentName("aaaa");
            System.out.println(student1);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
        }

        try{
            StudentDao mapper1 = sqlSession2.getMapper(StudentDao.class);
            Student student1 = mapper1.selectStudentById(1);
            System.out.println(student1);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            MybatisUtil.closeSession(sqlSession1);
        }
    }

运行结果:

总结:

  • 当readOnly为false时,修改返回的缓存对象(的拷贝),不会修改缓存中的数据。
  • 当readOnly为true时,修改返回的缓存对象(的相同实例),会修改缓存中的数据,影响下一次相同查询条件的查询结果。

 

Spring Boot和MyBatis框架可以很好地结合使用,并且支持二级缓存配置和使用。 二级缓存MyBatis提供的一种缓存机制,用于缓存查询结果,减少数据库的访问次数,提高系统性能。它是基于Mapper级别的缓存,不同的Mapper之间的缓存互相独立。在配置和使用二级缓存时,需要注意以下几点: 1. 在Spring Boot的配置文件,通过配置`mybatis.configuration.cache-enabled`属性来开启或关闭MyBatis二级缓存,默认情况下是开启的。 2. 在Mapper接口对应的XML文件,可以通过`<cache>`标签配置二级缓存的相关属性。例如: ```xml <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/> ``` - `eviction`属性表示缓存的清除策略,常用的有LRU(最近最少使用)和FIFO(先进先出)。 - `flushInterval`属性表示缓存刷新间隔时间,单位为毫秒,默认值为不刷新(即0)。 - `size`属性表示缓存的最大容量,默认值为不限制。 - `readOnly`属性表示是否只读缓存,默认值为false。 3. 在需要使用二级缓存的Mapper接口上添加`@CacheNamespace`注解,表示该Mapper接口启用了二级缓存。例如: ```java @CacheNamespace public interface UserMapper { // Mapper方法定义 } ``` 注意:需要确保Mapper接口所在的包路径在MyBatis的扫描路径,以使其被正确加载。 4. 在需要缓存的查询方法上添加`@Cacheable`注解,表示该方法的结果将被缓存。例如: ```java @Cacheable User getUserById(Long id); ``` 通过`@Cacheable`注解,可以指定缓存的key、缓存的条件等。 需要注意的是,二级缓存只适用于单个应用的情况,如果是分布式部署的多个应用,建议使用分布式缓存来替代二级缓存。此外,对于频繁写操作的数据,也不适合使用二级缓存。 以上就是关于Spring Boot和MyBatis二级缓存的简单介绍和配置方法。希望能对你有所帮助!
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值