2021-01-18

mybatis的缓存

 缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。

img

一级缓存基于sqlSession,二级缓存基于namespace

一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。一旦发生增删改,缓存立即失效

image-20210114200651033

测试

           IStudentDao studentDao = sqlSession.getMapper(IStudentDao.class);
​
            // 两次查询操作,只有第一次调用sql
            Student student =  studentDao.findStudentById(45);
            student = studentDao.findStudentById(45);
​
            Student student1 = new Student();
            student1.setName("xiaoming");
            student1.setId(46);
            student1.setSex("F");
​
//            studentDao.updateStudent(student1);
//            System.out.println("----------------");
​
            //清空缓存
            sqlSession.clearCache();
            //当同一个session 发生了 增删改时 那么换粗立即失效 ===  sqlSession.clearCache();
            student = studentDao.findStudentById(45);

二级缓存

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

缓存流程图

img

img

二级缓存的开启

1.在mybatis开启

    <settings>
​
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

2.在mapper 中开启二级缓存

<mapper namespace="com.wgz.dao.IStudentDao">
​
    <!--开启二级缓存-->
    <cache  >
     <property name="eviction" value="LRU" />
        <property name="flushInterval" value="60000" />
        <property name="size" value="1024" />
        <property name="readOnly" value="false" />
    </cache>
</mapper>    
  • eviction

    : 缓存回收策略,有这几种回收策略

    • LRU - 最近最少回收,移除最长时间不被使用的对象

    • FIFO - 先进先出,按照缓存进入的顺序来移除它们

    • SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象

    • WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象

默认是 LRU 最近最少回收策略

  • flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值

  • readOnly: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改

  • size : 缓存存放多少个元素

  • type: 指定自定义缓存的全类名(实现Cache 接口即可)

  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

3.使用缓存

  • useCache="true" 默认为true

    <!--使用resultMap-->
    <select id="findStudentById" parameterType="java.lang.Integer" resultType="student" useCache="true">
              select * from student_tb where id=#{id}
        </select>
​
​

4测试

    // 3.获取session
            SqlSession sqlSession1 = sqlSessionFactory.openSession();
​
            //  4.使用sqlSession 创建代理对象
            IStudentDao studentDao1 = sqlSession1.getMapper(IStudentDao.class);
​
​
            Student student1 =  studentDao1.findStudentById(45);
            System.out.println("student1:"+student1);
            //清空缓存
            sqlSession1.close();
​
            // 获取新的SqlSession 但是sql 语句没有执行,这是应为 可以去二级缓存去取,多个session 共享缓存
            SqlSession sqlSession2 = sqlSessionFactory.openSession();
            IStudentDao studentDao2  =  sqlSession2.getMapper(IStudentDao.class);
            Student student2=studentDao2.findStudentById(45);
            System.out.println("student2:"+student2);
​

注意

  • 一级二级缓存调用顺序:先查二级缓存,再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此;

  • 使用二级缓存的实体类必须实现序列化;方便存储

  • sqlsession只有关闭或者提交事务时才会把提交二级缓存,否则二级缓存不生效

  • sqlsession只要发生增删改就会把所有缓存清除,二级缓存只有当前命名空间发生增删改,命名空间的缓存才会清除

mybatis 一级缓存和二级缓存产生脏数据的问题?

使用二级缓存,当一个session发生修改时,另一个session如果缓存过以前的一级查询结果可能会有脏数据

    // 1.读取配置文件
            inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
​
            // 2.配置连接功工厂
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
​
            // 3.获取sqlSession 就是一个会话 连接  mybatis 默认是开启事务的
            sqlSession1 = sqlSessionFactory.openSession();
            IStudentDao iStudentDao1 = sqlSession1.getMapper(IStudentDao.class);
            System.out.println("使用sqlSession1 查询");
             Student student1 =  iStudentDao1.findStudentById(63);
            System.out.println("student1:"+student1);
​
​
​
            //sqlSession1.close();
​
​
​
​
            sqlSession2 = sqlSessionFactory.openSession();
            IStudentDao iStudentDao2 = sqlSession2.getMapper(IStudentDao.class);
            System.out.println("使用sqlSession2 查询");
            Student student2 = iStudentDao2.findStudentById(63);
            System.out.println("student2:"+student2);
​
​
            student2 = iStudentDao2.findStudentById(63);
            System.out.println("student2:"+student2);
​
            // 在 session2中修改数据  此时二级缓存失效
            student2.setHeight(1600);
            iStudentDao2.updateStudent(student2);
            sqlSession2.commit();
​
            // 因为二级缓存实现,iStudentDao1 先去二级缓存取,失效改用取一级缓存,此时能拿到数据但是不是    session2中修改数据的最新数据产生 脏数据
            student1 =  iStudentDao1.findStudentById(63);
            System.out.println("student1:"+student1);
​
​

解决方法

第一种.关闭一级缓存,并未查询的session改为自动提交

在spring中也不一定为发生,一般为单例的mapper每次创建新的sqlsession(若开启事务则时同一sqlsession),所以可以避免,不用担心

​
        <!-- 将一级缓存修改为STATEMENT 不使用一级缓存
                            SESSION  使用一级缓存
        -->
       <setting name="localCacheScope" value="STATEMENT"/>

 

  public static void main(String[] args) {
​
        InputStream inputStream = null;
        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        try {
            // 1.读取配置文件
            inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
​
            // 2.配置连接功工厂
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
​
            // 3.获取sqlSession 就是一个会话 连接  mybatis 默认是开启事务的
            sqlSession1 = sqlSessionFactory.openSession(true);
            IStudentDao iStudentDao1 = sqlSession1.getMapper(IStudentDao.class);
            System.out.println("使用sqlSession1 查询");
             Student student1 =  iStudentDao1.findStudentById(63);
            System.out.println("student1:"+student1);
​
​
​
            //sqlSession1.close();
​
​
​
​
            sqlSession2 = sqlSessionFactory.openSession();
            IStudentDao iStudentDao2 = sqlSession2.getMapper(IStudentDao.class);
            System.out.println("使用sqlSession2 查询");
            Student student2 = iStudentDao2.findStudentById(63);
            System.out.println("student2:"+student2);
​
​
            student2 = iStudentDao2.findStudentById(63);
            System.out.println("student2:"+student2);
​
            // 在 session2中修改数据  此时二级缓存失效
            student2.setHeight(333);
            iStudentDao2.updateStudent(student2);
            sqlSession2.commit();
​
            Thread.sleep(4000);
​
            // 因为二级缓存实现,iStudentDao1 先去二级缓存取,失效改用取一级缓存,此时能拿到数据但是不是    session2中修改数据的最新数据产生 脏数据
           Student student3 =  iStudentDao1.findStudentById(63);
           System.out.println("student3:"+student3);
​
​
​
        } catch (Exception e) {
            e.printStackTrace();
            // 有异常回滚
            sqlSession1.rollback();
​
        }finally {
            // 5.释放资源
            sqlSession1.close();
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
​
    }
​

第二种:不使用缓存

<select id="findStudentById" parameterType="java.lang.Integer" resultType="com.baidu.entity.Student"  useCache="false" >
​
        <include refid="selectAll"></include> where id = #{id}
​
    </select>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值