Mybatis 缓存配置

一级缓存

  • 基于PerpetualCache的HashMap本地缓存
  • 作用域为sqlsession
  • 当sqlsession flush或close后,该sqlsession的所有Cache都会清空。
  • 默认开启。

介绍

在一次数据库会话中,执行相同SQL语句,会优先命中一级缓存,避免直接去数据库查询,提高性能。
每个SqlSession都持有Executor,每个Executor都有一个LocalCache,当用户发起查询时,Mybatis会根据用户当前执行语句生成MapperStatement,在LocalCache进行查询,如果缓存命中,直接返回结果给用户。如果缓存没有命中,查询数据库,结果写入LocalCache,最后返回结果给用户。
一级缓存

实验1 多次相同查询

public static void main(String[] args) throws IOException {
    //获取sqlSession会话
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //获取Mapper
    DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
    System.out.println(("查询第1次:{}"+deptMapper.selectDept(5)));
    System.out.println(("查询第2次:{}"+deptMapper.selectDept(5)));
    System.out.println(("查询第3次:{}"+deptMapper.selectDept(5)));
    //关闭会话
    sqlSession.close();
}

主动调用的三次查询,实际只有第一次真正的去数据库查询,后续查询使用一级缓存。
一级缓存测试

实验2 修改数据后再查询

    public static void main(String[] args) throws IOException {
        //获取sqlSession会话
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        System.out.println(("查询第1次:{}"+deptMapper.selectDept(5)));
        System.out.println(("查询第2次:{}"+deptMapper.selectDept(5)));
        System.out.println(("查询第3次:{}"+deptMapper.selectDept(5)));
        Map<String,String> map=new HashMap<>();
        map.put("name","执行Update方法");
        map.put("id","5");
        System.out.println(("执行update方法----------------------------------------------"));
        deptMapper.updateDept(map);
        System.out.println(("执行update方法----------------------------------------------"));
        System.out.println(("查询第4次:{}"+deptMapper.selectDept(5)));
        //关闭会话
        sqlSession.close();
    }

当对数据库发生了修改操作,再执行相同查询时,一级缓存会失效。
修改数据后缓存失效

实验3 sqlsession会不会缓存共享

    public static void main(String[] args) throws IOException {
        //获取sqlSession会话
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        //获取Mapper
        DeptMapper deptMapper1 = sqlSession1.getMapper(DeptMapper.class);
        DeptMapper deptMapper2 = sqlSession2.getMapper(DeptMapper.class);
        System.out.println(("sqlSession1查询第1次:{}"+deptMapper1.selectDept(5)));
        System.out.println(("sqlSession2查询第1次:{}"+deptMapper2.selectDept(5)));
        Map<String,String> map=new HashMap<>();
        map.put("deptName","执行Update方法");
        map.put("id","5");
        System.out.println(("执行update方法----------------------------------------------"));
        System.out.println(("sqlSession1修改数据:{}"+deptMapper1.updateDept(map)));

        System.out.println(("sqlSession1查询第2次:{}"+deptMapper1.selectDept(5)));
        System.out.println(("sqlSession2查询第2次:{}"+deptMapper2.selectDept(5)));

        //关闭会话
        sqlSession1.close();
        sqlSession2.close();
    }

可以看出SqlSession2查询到了脏数据,SqlSession1中已经修改了数据,而SqlSession2仍然获取自己缓存的旧数据。证明:一级缓存只在数据库会话内部共享。
在这里插入图片描述

一级缓存执行时序图

在这里插入图片描述

一级缓存—源码分析

  • SqlSession提供操作数据库的方法,和数据库操作的相关的职责都会委派给Executor。
    在这里插入图片描述

  • 所有的数据库操作都会通过Executor来执行。
    在这里插入图片描述

  • Executor创建时
    在这里插入图片描述
    在这里插入图片描述

  • Executor实现类
    实现类

  • BaseExecutor: BaseExecutor是一个实现了Executor接口的抽象类,定义若干抽象方法,在执行的时候,把具体的操作委托给子类进行执行。
    在这里插入图片描述

  • 在上面的时序表中可以看出LocalCache的查询和写入都在Executor的内部完成。在BaseExecutor的成员变量中存在LocalCache的变量。
    在这里插入图片描述

  • 当执行SQL时,会通过defaultSqlSession的selectList方法,再调用BaseExecutor的query()方法,根据传入的key,从LocalCache中获取缓存数据。
    在这里插入图片描述

  • Cachekey的获取过程一个初始乘数和hashcode,共同维护一个updateList,在update方法中通过计算后,将对象添加到updataList中。最后重写equals方法,Key的比对方法,对hashcode,checksum,count进行比较,还对updateList中所有元素遍历比较。只有两条SQL的五个值完全相同,才判断为相同SQL。

    Statement ID+Offset + Limit +SQL +Params

    在这里插入图片描述
    在这里插入图片描述

  • 如果缓存中获取数据为空,就去数据库中查询queryFromDataBase。
    在这里插入图片描述

  • 只要进入数据库查询,就删除当前的之前的缓存,放入新的缓存。
    在这里插入图片描述

  • 最后对一级缓存作用域进行判断,是不是STATEMENT,如果是就清空缓存。
    在这里插入图片描述

  • 执行Update会清空缓存。clearLocalCache();
    在这里插入图片描述

  • 总结:

    • 一级缓存的生命周期与SqlSession一致。
    • 内部设计简单,采用没有容量限定的HashMap。
    • 一级缓存的最大范围就是SqlSession内部,在多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,缓存基本应该设置为Statement。

二级缓存

开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询
当开启缓存后,会先查询二级缓存——>一级缓存——>数据库
在这里插入图片描述

开启二级缓存配置

  • Mybatis-config.xml中开启缓存

    <setting name="cacheEnabled" value="true"/>
    
  • MyBatis的映射XML中配置cache或者 cache-ref

    • Cache声明这个namespace使用二级缓存
       <cache />
       type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
       eviction: 定义回收的策略,常见的有FIFO,LRU。
       flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
       size: 最多缓存对象的个数。
       readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
       blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
      
    • Cache-ref代表引用别的命名空间的Cache配置
      <cache-ref namespace="mapper.DeptMapper"/>
      
    • 二级缓存特点
      • sqlsession没有调用commit()方法时,二级缓存没有起作用
      • 当提交事务时,sqlSession1查询完数据后,sqlsession2的相同的查询,使用了缓存,缓存的命中率是0.5。
      • 更新数据库并提交事务后,sqlsession2的查询走了数据库,没有走Cache。
      • MyBatis的二级缓存不适应用于映射文件中存在多表查询的情况,namespace级别,不能感应其他namespace的变化; 可以使用cache-ref使用同一个缓存空间,但是造成缓存粒度变粗。

Cache接口

  • SynchronizedCache:同步Cache,实现比较简单,直接使用synchronized修饰方法。
  • LoggingCache:日志功能,装饰类,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。
  • SerializedCache:序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的Copy,用于保存线程安全。
  • LruCache:采用了Lru算法的Cache实现,移除最近最少使用的Key/Value。 PerpetualCache:
    作为为最基础的缓存类,底层实现比较简单,直接使用了HashMap。

总结

MyBatis更适合单纯作为一个ORM框架使用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值