Mybatis源码学习笔记(五)之Mybatis框架缓存机制原理解析

news2024/10/1 1:29:03

1 Mybatis框架的缓存模块

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。Mybatis框架中的缓存分为一级缓存和二级缓存,三级缓存基本都要借助自定义缓存或第三方服务来进行实现。但本质上是一样的,都是借助Cache接口实现的。缓存模块在Mybatis的源码结构中是在 org.apache.ibatis.cache包下面存放着的, 如下图:
在这里插入图片描述

2 Cache接口

Cache接口是缓存模块中最核心的接口, Mybatis框架中的实现类如下图所示,起始核心的实现类就只有一个, 就是
PerpetualCache类。
在这里插入图片描述
其他的实现类都是通过装饰者模式对PerpetualCache类的功能扩展和增强, 有兴趣可以自行查看源码。

2.1 PerpetualCache实现类的基本实现

核心方法都比较简单,都是一些很容易理解的方法,说白了就是对Map集合基本操作的封装。
在这里插入图片描述

2.2 Mybatis中缓存机制和属性

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML
映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

2.3 Mybatis中缓存属性说明

这些属性可以通过 cache 元素的属性来修改。比如:

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

翻译一下上面这行代码:

    1. eviction属性代表缓存清除策略

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

Mybatis默认清除策略是 LRU

    1. flushInterval属性代表缓存刷新时间间隔

刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。
默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

    1. size属性代表缓存对象数量

引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024

    1. readOnly属性代表缓存对象是否只读

(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。
因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。
速度上会慢一些,但是更安全,因此默认值是 false

注意:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的
insert/delete/update 语句时,缓存会获得更新。

3 Cache装饰器

3.1 为什么Myabtis框架要提供那么多的缓存装饰器

首先通过本文第2小节我们已经知道了Mybatis框架提供了一个Cache接口,同时提供了一个基本的缓存实现PerpetualCache,基本的缓存功能都已经提供了,但是这里有没有什么问题?

这里直接抛出几个问题, 大家思考一下:

  • 问题1: 你说缓存清除策略有好多种,如果我现在不想用LRU策略,我想改成其他的策略,你怎么让我通过简单的配置就能实现?
  • 问题2: 高并发的情况下, 怎么保证缓存操作的安全?
  • 问题3: 在开启事务的情况下,因为事务中可能执行的一组SQL指令,每个指令可能都会涉及到缓存操作,怎么保证缓存的批量操作及安全性?
  • 问题4: 缓存毕竟是在堆中开辟的内存空间,会消耗内存, 当内存占用超过一定比例我想把数据持久化到磁盘,怎么操作?
  • 问题5:在缓存的操作时我希望通过日志文件或者控制台打印的方式看到缓存操作的结果,比如缓存命中的结果,存入缓存的结果等等信息,该怎么实现?

这些问题大家想想,如果只依赖基础的PerpetualCache实现类, 能帮我们实现么, 肯定是不能的,需要我们自己去实现么? 肯定也是不需要的, Mybatis框架的设计者就这些问题结合设计模式为我们提供了现成的解决方案,就是一下这些缓存装饰器, 我们可以通过多个组合使用来实现我们在实际业务中的一些特殊的需求。

缓存实现类实现类说明装饰缓存属性条件主要功能和作用
基本缓存缓存基本实现类默认是PerpetualCache,也可以自定义比如RedisCache、EhCache等,具备基本功能的缓存类
LruCacheLRU策略的缓存eviction=“LRU”(默认)当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use)
FifoCacheFIFO策略的缓存eviction=“FIFO”当缓存到达上限时候,删除最先入队的缓存
SoftCacheWeakCacheeviction="SOFT"eviction=“WEAK”带清理策略的缓存通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference和WeakReference
LoggingCache带日志功能的缓存Base比如:输出缓存命中率
SynchronizedCache同步缓存Base基于synchronized关键字实现,解决并发问题
BlockingCache阻塞缓存blocking=true通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现
SerializedCachereadOnly=false(默认)支持序列化的缓存将对象序列化以后存到缓存中,取出时反序列化
ScheduledCache定时调度的缓存flushInterval不为空在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时),如果是则清空缓存–即每隔一段时间清空一次缓存
TransactionalCache事务缓存在TransactionalCacheManager中用Map维护对应关系在二级缓存中使用,可一次存入多个缓存,移除多个缓存

下面通过SynchronizedCache实现类来进行说明,

3.2 SynchronizedCache

/*
 *    Copyright 2009-2022 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import org.apache.ibatis.cache.Cache;

/**
 * @author Clinton Begin
 * 这个实现类的作用就是处理在并发缓存操作的安全问题, 
 * 所有的方法都加了synchronized关键字来保证线程安全
 */
public class SynchronizedCache implements Cache {

  private final Cache delegate;

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public synchronized int getSize() {
    return delegate.getSize();
  }

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

  @Override
  public synchronized Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public synchronized void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

}

通过源码我们能够发现,SynchronizedCache本质上就是在我们操作缓存数据的实际调用方法上加了synchronized 同步锁保证了线程安全。其他具体实现类,大家可以自行查阅源码。

4 Mybatis框架中缓存应用

4.1 框架启动缓存初始化

4.1.1 缓存配置解析的几个相关点介绍

配置文件中缓存相关的配置参数解析
在这里插入图片描述
Myabtis框架中一级缓存和二级缓存默认是开启的
在这里插入图片描述
缓存默认的作用域是Session
在这里插入图片描述
Configuration初始化的时候会为我们的各种Cache实现类完成别名注册
在这里插入图片描述

4.1.2 缓存对象的创建及初始化过程

解析全局配置文件mybatis-config.xml时会解析cacheEnabled属性并赋值给Configuration类的cacheEnabled属性, 默认值为true
在这里插入图片描述
如果你启动了二级缓存的配置, 那么在解析*Mapper.xml配置文件时,也会解析响应的cache属性,创建Cache对象并设置到Configuration类的caches属性中区,源码中具体实现见下图:
在这里插入图片描述
上图中的最后一行builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);调用了MapperBuilderAssistant类中的useNewCache()方法, 具体代码实现细节见下图, 也很简单:
在这里插入图片描述

4.1.3 缓存初始化流程图

在这里插入图片描述

4.2 一级缓存

首先需要明白的是我们需要缓存的对象是什么? 基本上就可以猜测出来我们需要在框架的那个具体环节进行处理。

通常我们呢需要缓存的就是我们从数据库中匹配出来的数据,所以这个缓存的操作一定是发生在SQL语句执行阶段的, 在Mybatis框架中专门负责语句执行的就是Executor, 所有我们呢只需要在Executor的实现类中查找关于缓存的处理逻辑代码实现即可。

一级缓存也叫本地缓存(Local Cache)。

MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。

MyBatis的一级缓存是默认开启的,不需要做任何的配置。

如果我们需要关闭一级缓存, 只需要修改Configuration中localCacheScope属性的取值为STATEMENT即可。

4.2.1 一级缓存什么时候创建

在我们的Executor对象创建的时候, 我们的一级缓存localCache对象就已经创建了,Executor对象又是什么时候创建的,是在SqlSession对象创建的时候完成的创建。验证流程如下图:
在这里插入图片描述
一级缓存对象是在BaseExecutor的构造器执行的时候进行创建的, BaseExecutor的构造器是在new SimpleExecutor()的时候被调用的,Configuration类的newExecutor方法中创建了SimpleExecutor对象,
而Configuration类的newExecutor方法又在factory.openSession()方法执行时被调用。
所以结论就是在我们创建sqlSession对象完成的时候, 一级缓存对象就已经准备就绪了。

4.2.2 一级缓存如何关闭

Mybatis框架中提供了关闭一级缓存的示例代码,在BaseExecutor类的query()方法中,如下:
在这里插入图片描述

4.2.3 一级缓存的具体实现

一级缓存的实现是在BaseExecutor执行器实现类中实现的,具体代码如下:

 @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 执行查询方法
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }
  
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    // 在执行查询语句之前,需要确保执行器是正常开启的,否则抛异常
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // flushCache="true"时,即使是查询操作也要先清空一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      // 防止递归查询重复处理缓存
      queryStack++;
      // 查询一级缓存
      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);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      // 如果当前本地缓存的作用域设置为STATEMENT,则将一级缓存关闭(清空相当于关闭操作)
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

4.2.3 一级缓存的实现流程图

在这里插入图片描述

4.2.4 一级缓存的测试验证

验证思路, 根据上面的流程图设计, 如果一级缓存命令, 就不会执行数据库查询操作, 那么查询语句在控制台肯定就只会输出一次, 同时两次查询都可以查询出结果。按照这个思路我们写一个单元测试来验证一下:

1)验证一同一个SqlSession会话中多次调用同一个查询方法,验证一级缓存是否生效

验证代码

public static void main(String[] args) {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            System.out.println("------------------------------------------");
            libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

验证结果
在这里插入图片描述
验证结论:一级缓存只在同一sqlSession会话内的相同查询(方法相同、输入参数相同)才生效。

2)验证二不同SqlSession会话中调用同一个查询方法,验证一级缓存是否生效

验证代码

SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            SqlSession sqlSession1 = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            System.out.println("------------------------------------------");
            libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }

验证结果
在这里插入图片描述
验证结论:一级缓存的作用范围是"Session",只在Session会话级别才生效

4.3 二级缓存

二级缓存是用来解决一级缓存不能跨会话(SqlSession)共享的问题的,范围是Mapper级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。

Mybatis框架中设计的二级缓存默认也是开启的, 但是我们在应用层调用时, 需要进行一些配置才可以生效。

同样我们通过缓存的作用范围再来反推一下, 二级缓存应该是发生在什么时候, 二级缓存的默认的作用范围是在Statement, 必然也是在Executor执行的时候才会进行缓存操作。

同时应为全局的cacheEnabled属性默认为true, 所以在创建Executor对象的时候会被CachingExecutor装饰, 那么二级缓存相关的操作大概率是在CachingExecutor类中实现的,下面我们到源码中验证一下

4.3.1 二级缓存生效配置

第一步:在全局配置文件mybatis-config.xml文件的settings标签对中开启全局配置二级缓存开启的配置

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

在这里插入图片描述
第二步:在mapper映射配置文件**Mapper.xml文件的增加如下配置:

<mapper namespace="com.kkarma.mapper.LibBookMapper">
    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true" />	
</mapper>

4.3.2 二级缓存源码解析

4.3.2.1 二级缓存对象如何存放

Myabtis框架中对于缓存的存储都是在Configuration类中的caches属性中存储的。通过CacheKey进行一级和二级缓存的区分
在这里插入图片描述

4.3.2.2 二级缓存对象初始化过程

*Mapper.xml文件中的解析顺序如下:
在这里插入图片描述
二级缓存对象是在**Mapper.xml文件解析的时候进行初始化, 因为如果开启二级缓存,*Mapper.xml文件中势必会配置cache标签,会先解析cache标签为当前mapper.xml文件的命名空间创建一个Cache对象。
在这里插入图片描述
在这里插入图片描述
在创建mappedStatement对象时, 会将单前命名空间中解析出来的二级缓存对象设置到当前mappedStatement对象中去。
在这里插入图片描述
在这里插入图片描述

4.3.2.3 二级缓存的具体实现

二级缓存的操作是在CachingExecutor中完成的,

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建缓存KEY
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 执行查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取当前mapper接口对应的缓存对象, 这里就是二级缓存
    // 二级缓存作为MappedStatement对象的一个属性,之前我们已经解析过, mapper映射文件解析之后会生成对应的MappedStatement对象
    // 所以二级缓存肯定是在MappedStatement创建的时候进行的初始化,验证一下
    Cache cache = ms.getCache();
    if (cache != null) {
      // 如果当前方法需要在执行之前刷新缓存, 这里就执行刷新缓存操作
      flushCacheIfRequired(ms);
      // 如果当前mappedStatement对象启用缓存并且未设置resultHandler
      if (ms.isUseCache() && resultHandler == null) {
        // 这里是排除存储过程语句执行的干扰
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
          // 从二级缓存中查询结果
        List<E> list = (List<E>) tcm.getObject(cache, key);
        // 如果缓存没命中,从数据库中进行数据查询
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 将查询结果设置到二级缓存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        // 返回查询结果为调用者
        return list;
      }
    }
    // 如果当前mappedStatement对象未启用缓存或设置了resultHandler
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

4.3.3 如何关闭二级缓存

在这里插入图片描述
**加粗样式**
在这里插入图片描述
那么如果关闭二级缓存呢?
在这里插入图片描述

4.3.3 二级缓存的测试验证

2)验证二不同SqlSession会话中调用同一个查询方法,验证一级缓存是否生效

验证代码

public static void main(String[] args) {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            SqlSession sqlSession1 = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            // sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据
            // 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中
            sqlSession.commit();
            System.out.println("------------------------------------------");
            libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            sqlSession.close();
            sqlSession1.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

验证结果
在这里插入图片描述

验证结论:二级缓存的作用范围是"statement",不同的sqlSession会话针对与同一个mapper命名空间下的方法查询都会走二级缓存。

注意事项
在事务提交之前,并不会真正存储到二级缓存,而是先存储到一个临时属性,等事务提交之后才会真正存储到二级缓存。
这么做的目的就是防止脏读。
因为假如你在一个事务中修改了数据,然后去查询,这时候直接缓存了,那么假如事务回滚了呢?所以这里会先临时存储一下。
在这里插入图片描述
二级缓存的操作最终都是借助TransactionalCache装饰器来实现的。因为二级缓存需要开启事务。
具体代码请自行查看TransactionalCache类的实现

4.4 三级缓存

4.4.1 三级缓存概述

三级缓存一般都是自定义缓存。分布式缓存框架:我们系统为了提高系统并发和性能,一般对系统进行分布式部署(集群部署方式)不适用分布缓存, 缓存的数据在各个服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理.ehcache,redis ,memcache,caffeine缓存框架,这里演示caffeine和redis缓存框架来进行实现:

4.4.2 借助mybatis-caffeine实现三级缓存

这里使用的是mybatis-encache框架来实现:

项目地址: https://github.com/mybatis/caffeine-cache

文档地址:https://mybatis.org/caffeine-cache/

这个代码本质上就是通过拓展实现Mybatis框架的Cahce接口来实现的,有兴趣可以自行研究,代码实现也很简单,很容易看。

4.4.2.1 mybatis-caffeine三级缓存配置

第一步:在我们的pom文件中引入依赖

 <dependency>
   <groupId>org.mybatis.caches</groupId>
   <artifactId>mybatis-caffeine</artifactId>
   <version>1.0.0</version>
 </dependency>

第二步:开启二级缓存
在这里插入图片描述
在这里插入图片描述

第三步:将*Mapper.xml文件中的二级缓存类型替换成CaffeineCache

<mapper namespace="org.kkarma.mapper.LibBookMapper">
  <cache type="org.mybatis.caches.redis.CaffeineCache" />
</mapper>
4.4.2.2 三级缓存测试验证

验证代码

public static void main(String[] args) {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            SqlSession sqlSession1 = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            // sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据
            // 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中
            sqlSession.commit();
            System.out.println("------------------------------------------");
            libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            sqlSession.close();
            sqlSession1.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在这里插入图片描述

4.4.3 借助mybatis-redis实现三级缓存

这里使用的是mybatis-redis框架来实现:

项目地址: https://github.com/mybatis/redis-cache

文档地址:http://mybatis.org/redis-cache/index.html

这个代码本质上就是通过拓展实现Mybatis框架的Cahce接口来实现的,有兴趣可以自行研究,代码实现也很简单,很容易看。

4.4.3.1 mybatis-redis三级缓存配置

第一步:在我们的pom文件中引入依赖

 <dependency>
   <groupId>org.mybatis.caches</groupId>
   <artifactId>mybatis-redis</artifactId>
   <version>1.0.0-beta2</version>
 </dependency>

第二步:开启二级缓存
在这里插入图片描述
在这里插入图片描述
第三步:将*Mapper.xml文件中的二级缓存l类型替换成RedisCache

<mapper namespace="org.kkarma.mapper.LibBookMapper">
  <cache type="org.mybatis.caches.redis.RedisCache" />
</mapper>

第四步:在resoure目录下增加redis连接的相关配置文件redis.properties,文件内容如下:

host=***.***.***.***
port=****
password=********
databse=*

在这里插入图片描述

4.4.3.2 三级缓存测试验证

验证代码

public static void main(String[] args) {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            SqlSession sqlSession1 = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            // sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据
            // 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中
            sqlSession.commit();
            System.out.println("------------------------------------------");
            libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            sqlSession.close();
            sqlSession1.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

查询缓存中是否存在数据
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/374824.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

只会手工测试,裸辞后怎么才能找到工作

我们可以从以下几个方面来具体分析下&#xff0c;想通了&#xff0c;理解透了&#xff0c;才能更好的利用资源提升自己。 一、我会什么&#xff1f; 先说第一个我会什么&#xff1f;第一反应&#xff1a;我只会功能测试&#xff0c;在之前的4年的中我只做了功能测试。内心存在…

如何改变照片的大小kb?照片怎么改到100kb?

在平时的日常工作生活当中&#xff0c;我们都会遇到需要上传照片的情况&#xff0c;但是随着拍摄的照片越来越清晰照片体积也越来越大&#xff0c;很容易遇到图片太大上传不成功的情况&#xff0c;那么这时候应该怎么办呢&#xff1f;今天来给大家分享一款照片压缩器&#xff0…

TCP/IP协议,网络工程部分

这个博客参考了许多up主的视频和网上其他的博主的文章&#xff0c;还有我老师的ppt 这里是目录一、osi七层模型&#xff08;参考模型&#xff09;1.物理层2.数据链路层&#xff08;数据一跳一跳进行传递&#xff09;3.网络层&#xff08;端到端传输&#xff09;4.传输层&#x…

synchronized底层如何实现?什么是锁的升级、降级?

第16讲 | synchronized底层如何实现&#xff1f;什么是锁的升级、降级&#xff1f; 我在上一讲对比和分析了 synchronized 和 ReentrantLock&#xff0c;算是专栏进入并发编程阶段的热身&#xff0c;相信你已经对线程安全&#xff0c;以及如何使用基本的同步机制有了基础&#…

Web Spider案例 网洛者 第一题 JS混淆加密 - 反hook操作 练习(五)

文章目录一、资源推荐二、第一题 JS混淆加密 - 反hook操作2.1 过控制台反调试(debugger)2.2 开始逆向分析三、python具体实现代码四、记录一下&#xff0c;execjs调用混淆JS报错的问题总结提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、资源推荐 …

Echarts 实现电池效果的柱状图

第022个点击查看专栏目录本示例是解决显示电池电量状态的柱状图&#xff0c;具体的核心代码请参考源代码。 文章目录示例效果示例源代码&#xff08;共102行&#xff09;相关资料参考专栏介绍示例效果 示例源代码&#xff08;共102行&#xff09; /* * Author: 还是大剑师兰特…

aws ecs 使用application autoscaling自动扩缩ecs服务

参考资料 https://aws.amazon.com/cn/blogs/china/microservices-on-amazon-ecs-1/ https://aws.amazon.com/cn/blogs/china/microservices-on-amazon-ecs-2/ https://zhuanlan.zhihu.com/p/355383555 https://docs.amazonaws.cn/en_us/AmazonECS/latest/developerguide/ser…

YOOV人事管理|2023年面临7大职场趋势,关系到管理者和HR

各种停摆浪潮席卷了2022年的职场生态&#xff0c;对于人力资源工作者来说&#xff0c;无论是判断员工的意向&#xff0c;或是组织面对的挑战&#xff0c;都愈来愈复杂。YOOV人事管理针对2023年&#xff0c;提出了7项观察&#xff0c;提醒雇主和HR应留意的未来工作趋势。 1.安静…

95.【操作系统-第一章】

操作系统(一)、操作系统概述1.1_操作系统的概念、功能和目标(1).操作系统的定义(2).操作系统的功能和目标——作为系统资源的管理者(3).操作系统的功能和目标——向上层提供方便易用的服务(4).操作系统的功能和目标--作为用户和计算机硬件之间的接口(5).操作系统的功能和目标—…

腾讯前端二面常考vue面试题(附答案)

虚拟DOM真的比真实DOM性能好吗 首次渲染大量DOM时&#xff0c;由于多了一层虚拟DOM的计算&#xff0c;会比innerHTML插入慢。正如它能保证性能下限&#xff0c;在真实DOM操作的时候进行针对性的优化时&#xff0c;还是更快的。 MVVM的优缺点? 优点: 分离视图&#xff08;V…

PowerCommand康明斯发电机控制屏维修HMI211

康明斯柴油发电机的监控系统分为普通机组控制屏和智能化机组控制界面。普通操作界面实用于普通的康明斯柴油发电机的控制&#xff0c;康明斯柴油发电机的起动与停止、供电与断电、状态调整等均由手动操作&#xff1b;自动化康明斯柴油发电机控制系统适合于智能化康明斯柴油发电…

JavaWeb 基本概念

JavaWeb Java Web 1、基本概念 1.1、前言 web开发&#xff1a; web&#xff0c;网页的意思 &#xff0c; www.baidu.com静态web html&#xff0c;css提供给所有人看的数据始终不会发生变化&#xff01; 动态web 淘宝&#xff0c;几乎是所有的网站&#xff1b;提供给所有人…

ActiveMQ RabbitMQ Kafka RocketMQ

消息中间件的作用 1.正向作用 应用解耦/事件驱动 异步通信 流量削峰 2.反向作用 系统可用性降低 系统复杂性提高 一致性问题 ---------------------------------------------------------------------------------------------------------…

C语言数据结构(3)----无头单向非循环链表

目录 1. 链表的概念及结构 2. 链表的分类 3. 无头单向非循环链表的实现(下面称为单链表) 3.1 SListNode* BuySListNode(SLTDateType x) 的实现 3.2 void SListPrint(SListNode* plist) 的实现 3.3 void SListPushBack(SListNode** pplist, SLTDateType x) 的实现 3.4 voi…

【分布式】分布式场景下的稳定性保障

文章目录1、什么是稳定性保障2、明确稳定性保障目标2.1、明确一级目标2.2、拆解二级目标3、如何进行稳定性保障3.1、全链路梳理3.2、全链路压测3.3、集群扩容3.4、服务限流3.5、提前预案3.6、紧急预案3.7、系统监控4、大促稳定性保障4.1、制定大促计划4.2、大促准备4.3、大促值…

kubeadm方式安装k8s高可用集群(版本1.26x)

K8S官网&#xff1a;https://kubernetes.io/docs/setup/ 高可用Kubernetes集群规划 配置备注系统版本CentOS 7.9Docker版本20.10.xPod网段172.16.0.0/12Service网段10.103.10.0/16 主机IP说明k8s-master01 ~ 03192.168.77.101 ~ 103master节点 * 3k8s-master-lb192.168.77.2…

Tina_Linux配网开发指南

OpenRemoved_Tina_Linux_配网_开发指南 1 概述 1.1 编写目的 介绍Allwinner 平台上基于wifimanager-v2.0 的WiFi 配网方式&#xff0c;包括softap(WiFi ap 模式热点配网),soundwave(声波配网),BLE(蓝牙低功耗配网)。 1.2 适用范围 • allwinner 软件平台tina v5.0 版本及以…

锁相环的组成和原理及应用

一.锁相环的基本组成 许多电子设备要正常工作&#xff0c;通常需要外部的输入信号与内部的振荡信号同步&#xff0c;利用锁相环路就可以实现这个目的。 锁相环路是一种反馈控制电路&#xff0c;简称锁相环(PLL)。锁相环的特点是&#xff1a;利用外部输入的参考信号控制环路内…

Java查漏补缺(04)IDEA安装设置、JDK相关设置、详细设置、工程与模块管理、代码模板的使用、快捷键的使用、DEBUG断点调试、常用插件

Java查漏补缺&#xff08;04&#xff09;IDEA安装设置、JDK相关设置、详细设置、工程与模块管理、代码模板的使用、快捷键的使用、DEBUG断点调试、常用插件本章专题与脉络1. 认识IntelliJ IDEA1.1 JetBrains 公司介绍1.2 IntelliJ IDEA 介绍1.3 IDEA的主要优势&#xff1a;(vs …

Ubuntu下不能切换中文,qt creator无法输入中文,sogo输入法(详细步骤)

目录&#xff1a;1、解决ubuntu 不支持切换中文&#xff0c;并安装sogo输入法步骤&#xff1b;2、解决Qt Creator不支持中文输入&#xff1a;详细步骤&#xff1a;一、解决ubuntu 不支持切换中文&#xff0c;并安装sogo输入法步骤&#xff1a;1、如果在键盘输入法系统中&#x…