精华内容
下载资源
问答
  • mybatis缓存
    千次阅读
    2021-12-28 11:11:17

    1. MyBatis缓存

    1.1 MyBatis缓存概述

    MyBatis作为目前最常用的ORM数据库访问持久层框架,其本身支持动态SQL存储映射等高级特性也非常优秀,通过Mapper文件采用动态代理模式使SQL与业务代码相解耦,日常开发中使用也非常广泛,本文主要讨论mybatis缓存功能,mybatis缓存本身设计初衷是为了解决同一会话相同查询的效率问题,单机环境下也确实起到了提高查询效率的作用,但是随着业务场景变化以及分布式微服务的出现,其弊端也渐渐显现出来,不同会话间操作数据,关联查询数据采用mybatis缓存时会存在出现脏数据的风险。

    1.2 MyBatis一二级缓存区别

    1.Mybatis一级缓存是SQLSession级别的,一级缓存的作用域是SQlSession;Mabits一级缓存默认是开启的。 在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 在同一次会话中执行两次相同查询中间执行了更新操作的时候,缓存会被清空,第二次相同查询仍然会去查询数据库。

    2.Mybatis二级缓存是Mapper级别的,二级缓存的作用域是全局的,多个SQlSession共享的,二级缓存的作用域更大;Mybatis二级缓存默认是没有开启的。 第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放在该mapper对应的二级缓存区域。 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。

    2. MyBatis一级缓存

    2.1 MyBatis一级缓存概述

    默认情况下,只启用了本地的会话缓存,也就是一级缓存,它仅仅对一个会话中的数据进行缓存。 mybatis一级缓存指的是在应用运行过程中,一次数据库会话中,执行多次相同的查询,会优先查询缓存中的数据,减少数据库查询次数,提高查询效率。
    每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。

    2.2 MyBatis一级缓存配置

    mybatis一级缓存默认是开启的,可根据需要选择级别是session或这statement。开发者只需在MyBatis的配置文件中,添加如下语句,就可以使用一级缓存。共有两个选项,session或者statement,默认是session级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是statement级别,可以理解为缓存只对当前执行的这一个Statement有效。

    <setting name="localCacheScope" value="SESSION"/>
    

    2.3 MyBatis一级缓存原理分析

    1.在初始化SqlSesion时,会使用Configuration类创建一个全新的Executor,作为DefaultSqlSession构造函数的参数。

        // newExecutor 尤其可以注意这里,如果二级缓存开关开启的话,是使用CahingExecutor装饰BaseExecutor的子类
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);                      
        }
    

    2.SqlSession创建完毕后,根据Statment的不同类型,会进入SqlSession的不同方法中,如果是Select语句的话,最后会执行到SqlSession的selectList,代码如下所示:

    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
          MappedStatement ms = configuration.getMappedStatement(statement);
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    }
    

    3.SqlSession把具体的查询职责委托给了Executor。如果只开启了一级缓存的话,首先会进入BaseExecutor的query方法。代码如下所示:

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    

    4.在上述代码中,会先根据传入的参数生成CacheKey,进入该方法查看CacheKey是如何生成的,代码如下所示:

    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    //后面是update了sql中带的参数
    cacheKey.update(value);
    

    在上述的代码中,将MappedStatement的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的参数传入了CacheKey这个类,最终构成CacheKey。以下是这个类的内部结构:

    public CacheKey() {
        this.hashcode = DEFAULT_HASHCODE;
        this.multiplier = DEFAULT_MULTIPLYER;
        this.count = 0;
        this.updateList = new ArrayList<Object>();
    }
    

    首先是成员变量和构造函数,有一个初始的hachcode和乘数,同时维护了一个内部的updatelist。在CacheKey的update方法中,会进行一个hashcode和checksum的计算,同时把传入的参数添加进updatelist中。如下代码所示:

    public void update(Object object) {
        int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
        count++;
        checksum += baseHashCode;
        baseHashCode *= count;
        hashcode = multiplier * hashcode + baseHashCode;
        
        updateList.add(object);
    }
    

    除去hashcode、checksum和count的比较外,只要updatelist中的元素一一对应相等,那么就可以认为是CacheKey相等。只要两条SQL的下列五个值相同,即可以认为是相同的SQL。
    Statement Id + Offset + Limmit + Sql + Params

    5.BaseExecutor的query方法继续往下走,代码如下所示:

    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
        // 这个主要是处理存储过程用的。
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    

    如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。

    在query方法执行的最后,会判断一级缓存级别是否是STATEMENT级别,如果是的话,就清空缓存,这也就是STATEMENT级别的一级缓存无法共享localCache的原因。代码如下所示:

    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache();
    }
    

    在源码分析的最后,我们确认一下,如果是insert/delete/update方法,缓存就会刷新的原因。
    SqlSession的insert方法和delete方法,都会统一走update的流程,代码如下所示:

    @Override
    public int insert(String statement, Object parameter) {
        return update(statement, parameter);
      }
       @Override
      public int delete(String statement) {
        return update(statement, null);
    }
    

    update方法也是委托给了Executor执行。BaseExecutor的执行方法如下所示:

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        clearLocalCache();
        return doUpdate(ms, parameter);
    }
    

    每次执行update前都会清空localCache。

    2.4 MyBatis一级缓存总结

    1.MyBatis一级缓存的生命周期和SqlSession一致。
    2.MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
    3.MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

    3. MyBatis二级缓存

    3.1 MyBatis二级缓存概述

    在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
    在这里插入图片描述

    3.2 MyBatis二级缓存配置

    要正确的使用二级缓存,需完成如下配置的。
    1.在MyBatis的配置文件中开启二级缓存。

    <setting name="cacheEnabled" value="true"/>
    

    2.在MyBatis的映射XML中配置cache或者 cache-ref 。
    cache标签用于声明这个namespace使用二级缓存,并且可以自定义配置。

    <cache/>   
    
    type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
    eviction: 定义回收的策略,常见的有FIFO,LRU。
    flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
    size: 最多缓存对象的个数。
    readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
    blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
    

    3.cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。

    <cache-ref namespace="mapper.StudentMapper"/>
    

    3.3 MyBatis二级缓存原理分析

    源码分析从CachingExecutor的query方法展开,源代码走读过程中涉及到的知识点较多,不能一一详细讲解,读者朋友可以自行查询相关资料来学习。
    CachingExecutor的query方法,首先会从MappedStatement中获得在配置初始化时赋予的Cache。

    Cache cache = ms.getCache();
    

    本质上是装饰器模式的使用,具体的装饰链是:
    SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache。

    以下是具体这些Cache实现类的介绍,他们的组合为Cache赋予了不同的能力。

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

    3.4 MyBatis二级缓存总结

    1.MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
    2.MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
    3.在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。

    4. MyBatis缓存测试

    测试案例地址:https://gitee.com/rjzhu/opencode/tree/master/mybatis-cache-demo

    /**
     * MyBatis缓存测试类
     */
    public class StudentMapperTest {
    
        private SqlSessionFactory factory;
    
        /**
         * 初始化SqlSessionFactory
         */
        @Before
        public void setUp() throws Exception {
            factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        }
    
        /**
         * 查看缓存配置是否生效
         * <setting name="localCacheScope" value="SESSION"/>
         * <setting name="cacheEnabled" value="true"/>
         */
        @Test
        public void showDefaultCacheConfiguration() {
            System.out.println("本地缓存范围: " + factory.getConfiguration().getLocalCacheScope());
            System.out.println("二级缓存是否被启用: " + factory.getConfiguration().isCacheEnabled());
        }
    
        /**
         * MyBatis缓存测试一
         * 测试:同一个会话,相同查询连续查询三次
         * 结果:第一次查询数据库,二三次查询从缓存读取
         * 结论:同一个会话,多次相同查询,只有第一次查询数据库,其他都是缓存中获取,提高了查询效率
         */
        @Test
        public void testLocalCache() {
            SqlSession sqlSession = factory.openSession(true); // 自动提交事务
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    
            //第一次查询数据库,二三次查询直接从缓存读取
            System.out.println("第一次查询:" + studentMapper.getStudentById(1));
            System.out.println("第二次查询:" + studentMapper.getStudentById(1));
            System.out.println("第三次查询:" + studentMapper.getStudentById(1));
    
            sqlSession.close();
        }
    
        /**
         * MyBatis缓存测试二
         * 测试:同一个会话,先查询,再新增,再次重复第一次查询
         * 结果:第一次与第二次查询都查询数据库,修改操作后执行的相同查询,查询了数据库,一级缓存失效。
         * 结论:同一个会话执行更新操作后缓存失效,源码中会清空缓存
         */
        @Test
        public void testLocalCacheClear() {
            SqlSession sqlSession = factory.openSession(true); // 自动提交事务
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    
            //第一次与第二次查询都查询数据库,修改操作后执行的相同查询,查询了数据库,一级缓存失效。
            System.out.println("第一次查询:" + studentMapper.getStudentById(1));
            System.out.println("增加了" + studentMapper.addStudent(StudentEntity.builder().name("明明").age(20).build()) + "个学生");
            System.out.println("第二次查询:" + studentMapper.getStudentById(1));
    
            sqlSession.close();
        }
    
        /**
         * MyBatis缓存测试三
         * 测试:同时开启两个会话,会话一连续两次查询,会话二更新操作,会话一再次相同查询,会话二相同查询
         * 结果:会话一第一次查询数据库,第二次查询缓存,会话二更新完成,会话一再次相同查询仍然查询缓存(读取脏数据),会话二查询数据库获取最新数据。
         * 结论:缓存作用范围是一个会话当中,当其中有会话更新数据,其他会话会读取到脏数据
         */
        @Test
        public void testLocalCacheScope() {
            //开启两个SqlSession,在sqlSession1中查询数据,使一级缓存生效,在sqlSession2中更新数据库
            //验证一级缓存只在数据库会话内部共享。
            SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
            SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
    
            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑", 1) + "个学生的数据");
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
        }
    
        /**
         * MyBatis缓存测试四
         * 测试:同时开启两个会话,两个会话执行相同的查询
         * 结果:两次都是查询数据库
         * 结论:缓存作用范围是一个会话当中,不同会话,即使是相同查询,只使用各自的缓存
         */
        @Test
        public void testCacheWithoutCommitOrClose() {
            SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
            SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
    
            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
            //两次都是从数据库读取,说明需要提交事务,第二次查询才能走缓存
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    
        }
    
        /**
         * MyBatis缓存测试四
         * 测试:同时开启两个会话,两个会话执行相同的查询
         * 结果:两次都是查询数据库
         * 结论:缓存作用范围是一个会话当中,不同会话,即使是相同查询,只使用各自的缓存
         */
        @Test
        public void testCacheWithCommitOrClose() {
            SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
            SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
    
            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
            //第一次提交以后,第二次走缓存
            System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
            sqlSession1.close();
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    
        }
    
        /**
         * MyBatis缓存测试五
         * 测试:同时开启三个会话,通过接口方式,会话一查询后提交事务,会话二执行相同查询,缓存查询,会话三更新提交事务,会话二查询缓存
         * 结果:只有第一次查询数据库,其余都是查询缓存
         * 结论:只有提交事务以后,后续相同查询才会查询缓存,否则查询数据库
         */
        @Test
        public void testCacheWithUpdate() {
            SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
            SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
            SqlSession sqlSession3 = factory.openSession(true); // 自动提交事务
    
            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
            StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
    
            System.out.println("studentMapper1读取数据: " + studentMapper.getStudentById(1));
            sqlSession1.close();
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
    
            studentMapper3.updateStudentName("方方", 1);
            sqlSession3.commit();
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
        }
    
        /**
         * MyBatis缓存测试六
         * 测试:测试关联查询,出现脏数据问题,
         * 结论:不同会话之间关联查询的时候,其中会话更新单独更新关联的其中一个表,另一个会话感知不到,在不同的mapper文件中,缓存查询会出现脏数据情况
         */
        @Test
        public void testCacheWithDiffererntNamespace() {
            // 设置自动提交事务
            SqlSession sqlSession1 = factory.openSession(true);
            SqlSession sqlSession2 = factory.openSession(true);
            SqlSession sqlSession3 = factory.openSession(true);
    
            StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class);
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
            ClassMapper classMapper3 = sqlSession3.getMapper(ClassMapper.class);
    
            System.out.println("studentMapper1读取数据: " + studentMapper1.getStudentByIdWithClassInfo(1));
            sqlSession1.close();
    
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentByIdWithClassInfo(1));
    
            //更新数据
            classMapper3.updateClassName("特色一班", 1);
            sqlSession3.commit();
    
            //读取到脏数据,studentMapper2读取数据: StudentEntity(id=1, name=方方, age=16, className=一班)
            System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentByIdWithClassInfo(1));
        }
    
    }
    

    5. 参考文档

    MyBatis中文网:https://mybatis.net.cn/index.html
    MyBatis英文网:https://mybatis.org/mybatis-3/index.html
    MyBatis执行流程源码分析:https://blog.csdn.net/m0_37583655/article/details/122115750
    聊聊MyBatis缓存机制
    mybatis一级缓存和二级缓存的区别是什么

    更多相关内容
  • mybatis缓存

    2018-11-29 16:41:54
    mybatis支持缓存,如果我们查找数据库中某一条记录时,先从缓存中获取,如果缓存中不存在该记录,则从数据库中获取,在放入到缓存中。该文档是关于mybatis使用一级或二级缓存的介绍
  • Mybatis缓存

    2022-04-15 21:47:36
    1、什么是缓存[Cache]? 存在内存中的临时数据。 将用户经常查询的数据放在缓存(内存)中...Mybatis缓存 Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。 Myb.

    在这里插入图片描述

    1、什么是缓存[Cache]?

    存在内存中的临时数据。
    将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库查询文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

    2. 为什么使用缓存?

    减少和数据库的交互次数,减少系统开销,提高系统效率。

    3. 什么样的数据能使用缓存?

    经常查询并且不经常改变的数据。【可以使用缓存】


    Mybatis缓存

    Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
    Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
    • 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

    一级缓存

    • 一级缓存也叫本地缓存:
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库

    测试步骤:

    1、开启日志!
    2、测试在一个Session中查询两次相同记录
    3、查看日志输出
    在这里插入图片描述
    缓存失效的情况:

    • 查询不同的东西;

    • 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
      在这里插入图片描述
      3、查询不同的Mapper.xml

    4、手动清理缓存!
    在这里插入图片描述
    小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!
    一级缓存相当于一个Map。


    二级缓存

    • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存;
    • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

    工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据就会放在自己对应的缓存(map)中;

    步骤:

    1、在mybatis-config.xml开启全局缓存

            <!--显示的开启全局缓存-->
            <setting name="cacheEnabled" value="true"/>
    
    

    2、在要使用二级缓存的Mapper中开启

        <!--在当前Mapper.xml中使用二级缓存-->
        <cache/>
    
    

    3、测试
    问题:如果没有自定义参数,则会报错,我们需要将实体类序列化

    Cause: java.io.NotSerializableException: com.kuang.pojo.User
    
    

    小结:

    只要开启了二级缓存,在同一个Mapper下就有效;
    所有的数据都会先放在一级缓存中;
    只有当会话提交或者关闭的时候,才会提交到二级缓存中!


    缓存原理
    在这里插入图片描述

    展开全文
  • 主要介绍了Mybatis 缓存原理及失效情况解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • MyBatis缓存

    千次阅读 2022-02-24 15:47:59
    2、MyBatis缓存的分类: (1)、一级缓存:同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况下, 只执行一次 SQL 语句 (前提缓存没有过期) (2)、二级缓存:Mybatis的二级缓存是指mapper映射文件。二级缓存...

    1、为什么使用缓存
    对于一些我们经常查询的并且不经常改变的数据,如果每次查询都要与数据库进行交互,那么大大降低 了效率,因为我们使用缓存,将一些对结果影响不大且经常查询的数据存放在内存中,从而减少与数据 库的交互来提高效率,这就是缓存的优势。
    2、MyBatis缓存的分类:
    (1)、一级缓存:同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况下, 只执行一次 SQL 语句 (前提缓存没有过期)
    (2)、二级缓存:Mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下 的mapper映射文件内容,多个SqlSession共享。

    一、一级缓存
    同一个SqlSession 对象, 在参数和 SQL 完全一样的情况下, 只执行一次 SQL 语句(前提缓存 没有过期)
    (1)、同一个SqlSession
    在这里插入图片描述

    运行结果为:
    在这里插入图片描述

    在日志和输出中: 第一次查询发送了 SQL 语句, 后返回了结果; 第二次查询没有发送 SQL 语句, 直接从缓存中获取了结果。 第一次的对象和第二次的对象是相同的。
    (2)、不同的SqlSession
    在这里插入图片描述

    运行结果为:
    在这里插入图片描述

    两次查询都从数据库中取出了数据。 虽然结果相同, 但是是两个不同的对象。
    (3)、清空缓存
    刷新缓存是清空这个 SqlSession 的所有缓存, 不单单是某个键。
    在这里插入图片描述

    运行结果为:
    在这里插入图片描述

    两次都发送了 SQL 语句, 同时两个对象不相同。
    Mapper.xml配置方式:
    在SysUserMapper.xml的selectByCondition方法上添加 flushCache=“true” 属性
    在这里插入图片描述

    一级缓存的总结:
    1、同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和结果存放在一个 Map 中,如果后续的键值一样,则直接从 Map 中获取数据;
    2、不同的 SqlSession 之间的缓存是相互隔离的;
    3、用一个 SqlSession , 可以通过代码或配置在查询前清空缓存;
    4、insert、update、delete 语句会清空缓存。

    二、二级缓存
    二级缓存指的是 mybatis中SqlSessionFactory对象的缓存 ,由同一个SqlSessionFactory对象 创 建的SqlSession共享缓存。
    配置步骤:
    1、在配置文件 mybatis-conf.xml 中配置,让mybatis支持二级缓存 。
    在这里插入图片描述

    2、在映射配置文件Mapper.xml中 开启支持二级缓存
    在这里插入图片描述
    在这里插入图片描述

    3、让当前操作支持二级缓存,在select标签 加上 useCache=“true”
    在这里插入图片描述

    三、缓存使用注意事项
    1、由于在更新时会刷新缓存, 因此需要注意使用场合:查询频率很高,更新频率很低时使用,即经常使用 select, 相对较少使用insert, update,delete。
    2、缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响。但刷新缓存是刷新整个 namespace 的缓存,也就是你 insert, update,delete 了一个, 则整个缓存都刷新了。
    3、最好在 「只有单表操作」 的表的 namespace 使用缓存, 而且对该表的操作都在这个 namespace 中。 否则可能会出现数据不一致的情况。

    展开全文
  • MyBatis缓存详解

    2022-02-27 21:25:31
    Mybatis版本3.5.7 为啥要提前申明下版本,因为该文章是基于Mybatis3.5.7 版本源码做的分析,有一些源码与以前旧版本有不一样的地方,没有说明版本的话会引导初学者迷惑。有的说1有的说2 不知道该看那个文章才是适用...

    Mybatis版本3.5.7

    为啥要提前申明下版本,因为该文章是基于Mybatis3.5.7 版本源码做的分析,有一些源码与以前旧版本有不一样的地方,没有说明版本的话会引导初学者迷惑。有的说1有的说2 不知道该看那个文章才是适用自己的,不管怎么,大致思想不会变化,最好建议大家学会使用后多看源码。万变不离其中。

    基本缓存问题

    • 什么是缓存?

    1.存在内存中的临时数据
    2.将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库 数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

    • 为什么使用缓存

    减少和数据库的交互次数,减少系统开销或IO,提高系统效率

    • 什么样的场景使用缓存?

    经常查询同时不经常修改的数据。

    Mybatis 缓存

    MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地 提升查询效率。

    一级缓存

    一级缓存:也称为本地缓存,基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为SqlSession,用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,各个SqlSession之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis默认打开一级缓存、不允许关闭。

    在这里插入图片描述

    二级缓存(默认是开启)

    也称为全局缓存,是mapper级别的缓存。二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,所以默认也是本地缓存。不同之处在于其存储作用域为 Mapper(Namespace),可以在多个SqlSession之间共享,是针对一个表的查结果的存储,可以共享给所有针对这张表的查询的用户。也就是说对于mapper级别的缓存不同的sqlsession是可以共享的,并且可自定义存储源,如 Ehcache、Redis。默认开启二级缓存,但是还需要配置才可以使用。
    在这里插入图片描述
    在这里插入图片描述

    查询流程

    一级缓存流程图

    在这里插入图片描述

    演示代码

     InputStream inputStream = Resources.getResourceAsStream(XML);
            SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSessionOne = sqlSessionFactory.openSession(false);
            SqlSession sqlSessionTwo = sqlSessionFactory.openSession(false);
            MpUserMapper sessionOneMapperOne = sqlSessionOne.getMapper(MpUserMapper.class);
            MpUser mpUser = sessionOneMapperOne.selectById(1L);
            MpUser mpUser2 = sessionOneMapperOne.selectById(1);
    

    二级缓存流程图

    在这里插入图片描述

    演示代码

    InputStream inputStream = Resources.getResourceAsStream(XML);
            SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSessionOne = sqlSessionFactory.openSession(false);
            SqlSession sqlSessionTwo = sqlSessionFactory.openSession(false);
            MpUserMapper sessionOneMapperOne = sqlSessionOne.getMapper(MpUserMapper.class);
            MpUserMapper sessionTwoMapperMapperOne = sqlSessionTwo.getMapper(MpUserMapper.class);
            MpUser mpUser = sessionOneMapperOne.selectById(1L);
            sqlSessionOne.commit();
            MpUser mpUser2 = sessionTwoMapperMapperOne.selectById(1);
    

    整体流程图

    在这里插入图片描述

    缓存源码分析

    一级缓存查询

    • 获取执行器设置到Sessioon

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

    • 获取代理Mappper
      在这里插入图片描述
      在这里插入图片描述

    • 查询分析
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    二级缓存查询

    • 首先配置二级缓存的存储位置我这次选择的是Redis 大家可以按照自己需求来选择,默认不建议,因为他也是本地的缓存,分布式情况还需要再次请求数据库缓存到本地。
    public class RedisCache implements Cache {
        private final String id;
        private static Jedis cache;
    
        static {
            cache = new Jedis("127.0.0.1", 6379);
            cache.auth("1111111");
        }
    
    
        public RedisCache(String id) {
            this.id = id;
        }
    
        @Override
        public String getId() {
            return id;
        }
    
        /**
         * 返回缓存所有键值对的数量
         *
         * @return
         */
        @Override
        public int getSize() {
            Long dbSize = cache.dbSize();
            return dbSize.intValue();
        }
    
        /**
         * 向缓存中存入数据
         *
         * @param key
         * @param value
         */
        @Override
        public void putObject(Object key, Object value) {
            byte[] keyBs = SerializationUtils.serialize((Serializable) key);
            byte[] valueBs = SerializationUtils.serialize((Serializable) value);
            cache.set(keyBs, valueBs);
        }
    
        /**
         * 从缓存中获取数据
         *
         * @param key
         * @return
         */
        @Override
        public Object getObject(Object key) {
            byte[] keyBs = SerializationUtils.serialize((Serializable) key);
            byte[] valueBs = cache.get(keyBs);
            if (valueBs != null) {
                return SerializationUtils.deserialize(valueBs);
            }
            return null;
        }
    
        /**
         * 清除缓存
         *
         * @param key
         * @return
         */
        @Override
        public Object removeObject(Object key) {
            byte[] keyBs = SerializationUtils.serialize((Serializable) key);
            byte[] valueBs = cache.get(keyBs);
            Object obj = SerializationUtils.deserialize(valueBs);
            cache.del(keyBs);
            return obj;
        }
    
        /**
         * 清空缓存
         */
        @Override
        public void clear() {
            cache.flushDB();
        }
    
        @Override
        public ReadWriteLock getReadWriteLock() {
            return null;
        }
    
        @Override
        public boolean equals(Object o) {
            if (getId() == null) {
                throw new CacheException("Cache instances require an ID.");
            }
            if (this == o) {
                return true;
            }
            if (!(o instanceof Cache)) {
                return false;
            }
    
            Cache otherCache = (Cache) o;
            return getId().equals(otherCache.getId());
        }
    
        @Override
        public int hashCode() {
            if (getId() == null) {
                throw new CacheException("Cache instances require an ID.");
            }
            return getId().hashCode();
        }
    }
    

    我使用的注解形式

    @CacheNamespace(implementation = RedisCache.class,flushInterval = 1)

    目前支持两种模式一种是在xml 配置 一种上面注解模式,为啥两种可以通过源码分析得出。
    在这里插入图片描述
    在这里插入图片描述
    这里简答说下上面截图就是设置二级缓存cache 对象的位置,可以发现一种是xml 解析赋值,一种注解解析赋值。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    最后切记缓存的实体对象需要实例化。
    在这里插入图片描述
    总结:缓存这里细节也很多,也有使用不当造成异常问题或者性能问题。特别是并发情况的嵌套查询。后面分析嵌套查询的延迟加载,以及为啥一级缓存要最开始要加占位符。

    展开全文
  • Mybatis缓存详解

    2022-01-11 15:10:57
    Mybatis缓存 所有的查询都要连接数据库 连接数据库耗费资源 如何能一次查询的结果给他暂存到一个可以直接取到的地方!!–>内存:缓存 我们再次查询相同数据的时候直接走缓存就不用走数据库了 一、缓存简介 ...
  • mybatis缓存机制详解

    千次阅读 热门讨论 2021-12-16 10:12:13
    mybatis提供了缓存机制减轻数据库压力,提高数据库性能。mybatis缓存分为一级和二级两种: 一级缓存:SqlSession级别的缓存缓存的数据只在SqlSession内有效 二级缓存:mapper级别的缓存,同一个namespace公用这...
  • MyBatis 缓存原理梳理

    2022-03-06 13:08:18
    一级缓存:当在一次数据库会话中,执行多次查询条件完全相同的SQL时,MyBatis提供了一级缓存的方案优化,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。每个SqlSession中持有了...
  • Spring Boot - Mybatis 缓存

    2021-09-01 13:55:59
    mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。mybaits提供一级缓存和二级缓存。 一级缓存 一级缓存是sqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据...
  • MyBatis缓存机制

    2021-10-10 22:34:32
    一级缓存和二级缓存二、一级缓存三、二级缓存3.1 mybatis自带的二级缓存3.1.1 代码测试二级缓存3.1.2 查询结果存入二级缓存的时机3.1.3 二级缓存相关配置四、整合EHCache4.1 EHCache简介4.2 整合操作五、缓存基本...
  • 像大多数的持久化框架一样, Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数, 从而提高性能。 Mybatis缓存分为一级缓存,二级缓存。 文章目录一、Mybatis 一级缓存二、Mybatis 二级缓存 一、...
  • 看这一篇Mybatis缓存,妈妈再也不用担心我的缓存问题了
  • Mybatis 缓存

    千次阅读 2019-06-02 17:43:09
    为什么使用缓存? 对于一些我们经常查询的并且不经常改变的数据,如果每次查询都要与数据库进行交互,那么大大... 一级缓存指的是 mybatis中SQLSession对象的缓存 当我们在执行查询操作时,查询的结果同时会存入Sq...
  • 深入理解MyBatis缓存

    2022-03-27 22:14:29
    Mybatis缓存
  • MyBatis 缓存原理解析

    2022-04-14 15:56:55
    为什么 MyBatis缓存 缓存在互联网系统中是非常重要的, 其主要作用是将数据保存到内存中, 当用户查询数据时, 优先从缓存容器中获取数据,而不是频繁地从数据库中查询数据,从而提高查询性能。而在 ORM 框架中...
  • mybatis缓存机制

    2022-06-09 10:23:04
      学习了mybatis缓存机制,现对其进行总结。
  • MyBatis 二级缓存 关联刷新实现1、MyBatis缓存介绍2、二级缓存问题2.1、数据不一致问题验证2.2、问题处理思路3、关联缓存刷新实现 1、MyBatis缓存介绍  Mybatis提供对缓存的支持,但是在没有配置的默认情况下,它只...
  • mybatis缓存引起的问题

    2022-02-21 23:22:23
    遇见一个大坑! 有时候mybatis多次查询条件一样时,会有缓存,此时可能缓存对象中修改了其他属性,造成两次查询的对象属性不完全一致,所以需要关闭缓存
  • mybatis缓存(一级缓存、二级缓存)

    千次阅读 2022-02-19 16:31:10
    一、什么是缓存? 1.什么是缓存 存储在内存当中的数据 ...二、Mybatis缓存 mybatis包含了一个非常强大的查询缓存特性,他可以非常方便的定制和配置缓存。缓存可以极大的提高查询的效率 mybatis系统当中
  • 05. Mybatis缓存问题

    2022-03-19 13:43:07
    Mybatis两种缓存的解释 1. 一级缓存 在学习一级缓存之前,我们先了解一下SqlSession SqlSession是对Connection的一层封装 如果要显式使用SqlSession,先使用输入流InputStream读取Mybatis的配置文件;再通过...
  • 总结⼀级缓存原理探究与源码分析⼆级缓存如何使用二级缓存开启⼆级缓存测试测试⼆级缓存和sqlSession无关测试执⾏commit()操作,⼆级缓存数据清空useCache和flushCache⼆级缓存整合Redispom⽂件配置⽂件Mapper....
  • mybatis缓存的一些问题

    2021-03-08 22:45:40
    mybatis缓存的一些问题 1、mybatis一级缓存可能导致的查出错误数据 可以看到,先从数据库中查出id为1的user,姓名为张三,然后将该user对象的姓名改为李四,然后重新查询id为1的user,由于一级缓存的存在,查询出的...
  • MyBatis缓存的使用 MyBatis的缓存分为一级缓存和二级缓存,一级缓存默认是开启的,而且不能关闭 至于一级缓存为什么不能关闭,MyBatis核心开发人员做出了解释:MyBatis的一些关键特性(例如通过<association>...
  • 文章目录一、缓存简介二、MyBatis 缓存三、MyBatis 缓存原理 一、缓存简介 什么是缓存缓存就是存在内存中的临时数据 将用户经常查询的数据放在缓存中,用户去查询数据就不需要再次访问数据库或者硬盘,这样就...
  • mybatis缓存配置

    千次阅读 2018-05-25 18:10:45
    mybatis缓存使示意图:一、一级缓存说明:其中一级缓存是mybatis默认使用的缓存,无需手动配置,二级缓存需要手动配置;一级缓存失效条件1)sqlSession不同,由于一级缓存是基于sqlSession级别的,所以当使用不同sq.....
  • lombok和mybatis缓存

    2022-05-11 09:39:11
    3、mybatis缓存 1、什么是缓存 [ Cache ]?(当然只有读走缓存,写是不走的)   缓存就是存在内存中的临时数据。   将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 127,979
精华内容 51,191
关键字:

mybatis缓存

友情链接: lanya_wZQ.rar