【Mybatis】一级缓存与二级缓存源码分析与自定义二级缓存

1.前言

缓存的本质就是内存,缓存在我们现在应用的价值是程序与数据库之间搭建的一个桥梁,提高用户的查询效率,尽量避免数据库的硬盘查询。

2.换出算法

LRU: 最不常用的对象会被换出到硬盘中(或者说最少使用的对象),通过给每个对象记录使用次数可以实现。

FIFO:先入先出,第一个进来的对象第一个将会被换出到硬盘中。

3.mybatis中如何使用缓存

源码解析

Cache类

 package org.apache.ibatis.cache;

 public interface Cache {


  String getId();


  void putObject(Object key, Object value);


  Object getObject(Object key);


  Object removeObject(Object key);


  void clear();

  int getSize();
  

  ReadWriteLock getReadWriteLock();

}


通过上述Cache接口源码中可以发现,这个缓存的存储结构很像Java语言中的Map集合.都有put方法根据key进行插入,和get方法通过key去获取数据.如果我们要使用Cache这一套缓存操作,那么就需要实现这个接口。

mybatis实现的缓存接口

在这里插入图片描述

可以发现只有PerpetualCache是属于impl包下,其他的实现类都是在decorators包下.因为Cache的实现使用了装饰器设计模式,decorators包下的实现类都是来对PerpetualCache进行功能增强所以说最终还是通过PerpetualCache为主。

在这里插入图片描述

PerpetualCache源码

 
package org.apache.ibatis.cache.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {

  private final String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @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();
  }

}

装饰器的增强功能

FifoCache 
	增强换出功能使用先入先出的方式进行换出操作
LruCache
	最少使用的缓存将被换出 (默认的装饰器)
LoggingCache
	Cache增加日志功能
BlockingCache
	保证同一时间只有一个线程到缓存中查找对应key的数据
ScheduledCache
	设置一个时间间隔来清空缓存
SerializbledCache
	自动完成key/value的序列化和反序列化操作
TransactionalCache
	只有在事务操作成功时,把对应的数据防止在缓存中

使用方法:

import org.apache.ibatis.cache.decorators.LoggingCache;
import org.apache.ibatis.cache.decorators.LruCache;
import org.apache.ibatis.cache.impl.PerpetualCache;

public class Test {
    /**
     * 用于测试:Cache 与 装饰器
     */
    public static void main(String[] args) {
        //创建PerpetualCache对象传入参数id
        PerpetualCache perpetualCache = new PerpetualCache("fei");
        System.out.println("获取缓存的 ID:" + perpetualCache.getId());
        //将PerpetualCache对象作为参数提供给LruCache
        LruCache lruCache = new LruCache(perpetualCache);
        // 设置一个缓存项
        lruCache.putObject("key", "my cache");
        //也可以使用将LRU换出算法作为参数提供给LoggingCache,使其拥有日志功能
        LoggingCache loggingCache = new LoggingCache(lruCache);
        System.out.println("获取一个缓存项:"+lruCache.getObject("key"));

    }
}

4.Cache在Mybaits应用

4.1 Mybatis缓存二层的体系

一级缓存 (默认开启)

一级缓存只对本SqlSession生效,换SqlSession,不能在利用原有SqlSession对应的一级缓存

1.Mybatis的查询流程。

Mybaits内部之所以能根据传入的接口,返回一个实现该接口的对象的原理就在于动态代理。

org.apache.ibatis.binding.MapperProxy-->cachedInvoker(method).invoke()-->MapperMethodInvoker
	mapperMethod.execute(sqlSession, args);-->executeWithResultHandler-->sqlSession.select
org.apache.ibatis.session.defaults.DefaultSqlSession-->select()
org.apache.ibatis.executor.Executor-->query()
org.apache.ibatis.executor.BaseExecutor-->query()
executor接口实现是通过BaseExecutor接口,然后有三个实现类,下面展示下executor的类图,这是适配器设计模式,在实现接口的过程中,只想或者只能实现其部分方法可以考虑适配器设计模式。

在这里插入图片描述

2.开始缓存源码分析

org.apache.ibatis.executor.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);
 }


  @Override
  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.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //缓存相关操作:如果从缓存中没有获取到数据执行else内容直接查询数据库,查询到直接返回list
      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();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }



private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //向缓存中存储一个枚举类型的占位符

    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //执行数据库查询操作
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    //将查询到的数据存储在缓存中
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

总结:

  • org.apache.ibatis.session.defaults.DefaultSqlSession: 一级缓存与SqlSession直接关联。每当查询被执行时,MyBatis首先会检查一级缓存中是否存在相应的数据。如果存在并且没有其他条件影响缓存的有效性(比如参数或时间戳),则返回缓存中的数据。
  • org.apache.ibatis.mapping.MappedStatement: 每个映射语句都绑定了一个内部资源,这个资源包含了该语句的配置信息,包括是否启用了缓存。
  • org.apache.ibatis.executor.Executor: MyBatis的执行器接口定义了查询的执行策略。默认情况下,它会检查一级缓存。
  • org.apache.ibatis.executor.CachingExecutor: 这个类实现了基于一级缓存的查询结果的缓存逻辑。它包装了另一个Executor实例,并在查询前检查是否有可用的缓存。

二级缓存(全局缓存)

激活二级缓存
1.mybatis-config.xml中加入setting的配置(现在版本已经不需要进行配置了,默认开启)
2.mapper文件中引入二级缓存Cache标签(必须要配置),还可以通过在mapper接口上添加@CacheNamespace注解来启用并配置二级缓存
3.事务存在

如下:
<!-- 在 MyBatis 配置文件中开启全局缓存 -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
 
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

<select id="findById" resultType="com.lagou.pojo.User" useCache="true">
    select * from user where id = #{id}
</select>

1.源码分析

org.apache.ibatis.executor.Executor
org.apache.ibatis.executor.CachingExecutor-->org.apache.ibatis.session.Configuration-->newExecutor()
	executor = new CachingExecutor(executor);//cacheEnabled:cacheEnabled = true;
//上面的cacheEnabled属性会使用套娃给executor增强缓存的功能,而这个cacheEnabled属性正是对应的我们mybatis-config配置文件中setting标签的配置.默认为true,这就是为什么现在不需要对setting进行配置的原因,但是需要在mapper文件中进行配置才会生效.

CachingExecutor是如何提供缓存操作

org.apache.ibatis.executor.CachingExecutor-->query()
  
  @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 {
     //首先从mappedStatement中获取缓存
    Cache cache = ms.getCache();
    //如果缓存不为空,那么判断mappedStatement中是否添加了isUserCache=true的属性,然后从缓存中获取数据.如果数据为空,则认为是第一次查询那么就会直接查询数据库然后将查询到的数据put到缓存中,最后返回list数据.
    if (cache != null) {
       //这里是如果sql语句加上了清除缓存的属性才会进行清除,查询默认为false也就是默认查询不清除缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //如果没有获取到缓存那么直接查询数据库
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
 //以上俩种查询数据库的操作虽然代码是一样的,但是设计是完全不同,缓存不为空时,并且在查询语句上加了Cache标签时,才会查询.而下面的是没有在mapper文件中写上Cache标签的查询

2.缓存类的创建

我们可以查看public Cache useNewCache

org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache

在这里插入图片描述

可以看到 有两个调用,一个是xml的方式,另外一个是使用注解的方式.分别对应了俩种方式去开启缓存操作。

  • org.apache.ibatis.builder.xml.XMLMapperBuilder
  • org.apache.ibatis.builder.annotation.MapperAnnotationBuilder

3.分析org.apache.ibatis.builder.xml.XMLMapperBuilder

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
     //当解析到配置文件的cache标签后,便会为我们在ms中创建cache也就是会调用useNewCache()方法
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
  
    private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      //LRU 默认的换出策略
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

所以总结下来流程就是:

Mybatis解析XML配置文件
---|Mybatis-config.xml
	---|Mapper 读取mapper.xml文件
		---|<cache>
			---|useNewCache()

具体构建 org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
      //可以看到这里使用构建者设计模式将零件组装创建对象
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

进入build()方法看看具体做了什么操作


  public Cache build() {
    //设置默认的实现
    setDefaultImplementations();
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }
  
  private void setDefaultImplementations() {
    //默认实现为PerpetualCache和下面的LruCache
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        //LruCache为PerpetualCache的装饰器
        decorators.add(LruCache.class);
      }
    }
  }
 

org.apache.ibatis.cache.decorators.LruCache

setSize()
在removeEldestEntry方法中,首先通过size()方法获取当前LinkedHashMap的大小,然后将其与传入的size参数进行比较。如果当前大小大于size,则表示需要移除最老的元素。在这种情况下,将最老元素的键赋值给eldestKey变量,并返回true表示移除成功。如果不需要移除元素,则返回false。

cycleKeyList()
将给定的键值对添加到keyMap中,并检查是否存在最老的键(eldestKey)。如果存在最老的键,则从委托对象(delegate)中删除该键,并将eldestKey设置为null。


build() ->Cache cache = newBaseCacheInstance(implementation, id);

  //可以发现传入的implementation参数是一个Cache的实现类
  private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
    Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);
    try {
        //通过反射创建Cache对象设置一个id
      return cacheConstructor.newInstance(id);
    } catch (Exception e) {
      throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
    }
  }

build() ->setCacheProperties(cache);

 //这里代码主要是为二级缓存设置一些参数例如cache标签中的一些Properties
 private void setCacheProperties(Cache cache) {
    if (properties != null) {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      for (Map.Entry<Object, Object> entry : properties.entrySet()) {
        String name = (String) entry.getKey();
        String value = (String) entry.getValue();
        if (metaCache.hasSetter(name)) {
          Class<?> type = metaCache.getSetterType(name);
          if (String.class == type) {
            metaCache.setValue(name, value);
          } else if (int.class == type
              || Integer.class == type) {
            metaCache.setValue(name, Integer.valueOf(value));
          } else if (long.class == type
              || Long.class == type) {
            metaCache.setValue(name, Long.valueOf(value));
          } else if (short.class == type
              || Short.class == type) {
            metaCache.setValue(name, Short.valueOf(value));
          } else if (byte.class == type
              || Byte.class == type) {
            metaCache.setValue(name, Byte.valueOf(value));
          } else if (float.class == type
              || Float.class == type) {
            metaCache.setValue(name, Float.valueOf(value));
          } else if (boolean.class == type
              || Boolean.class == type) {
            metaCache.setValue(name, Boolean.valueOf(value));
          } else if (double.class == type
              || Double.class == type) {
            metaCache.setValue(name, Double.valueOf(value));
          } else {
            throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
          }
        }
      }
    }
    if (InitializingObject.class.isAssignableFrom(cache.getClass())){
      try {
        ((InitializingObject) cache).initialize();
      } catch (Exception e) {
        throw new CacheException("Failed cache initialization for '" +
            cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
      }
    }
  }

继续

  public Cache build() {
    //1 设置默认的实现
    setDefaultImplementations();
    //2 创建Cache
    Cache cache = newBaseCacheInstance(implementation, id);
    //3 设置Cache属性
    setCacheProperties(cache);
    //4 如果没有对缓存进行拓展,也就是拓展其他的装饰器那么执行以下代码
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        //可以进行自定义装饰器
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      //直接使用默认的装饰器
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { //如果拓展了日志功能那么将日志功能套进cache

      cache = new LoggingCache(cache);
    }
    return cache;
  }

setStandardDecorators()分析

//获取相关参数(根据cache标签中是否增加对应的属性来判断添加对应的装饰器) 就是Cache下面的扩展类
private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

通过以上源码得知useNewCache创建缓存的具体流程应该是

useNewCache()
1.创建默认缓存---> PerpetualCache和 LruCache
2.创建新的实现---> Cache cache = newBaseCacheInstance(implementation, id);
3.读取整合cache的property标签增加额外的参数(内置缓存不用,主要用于自定义缓存 如redis)
4.增加装饰器----->cache标签上的属性如size,clearInterval,readWrite,blocking等

总结:

  • org.apache.ibatis.cache.Cache: 这是MyBatis缓存的接口,定义了用于操作缓存的方法,如put, get, remove, 和clear
  • org.apache.ibatis.cache.impl.PerpetualCache: 一个实现了Cache接口的类,代表一个永久的缓存,它不会自动清除。
  • org.apache.ibatis.cache.impl.BlockingCache: 一个线程安全的缓存实现,它使用锁来保证并发访问时的一致性。
  • org.apache.ibatis.cache.impl.FifoCacheLruCache等: 这些类提供了不同的缓存回收策略,如FIFO(先进先出)和LRU(最近最少使用)。
  • org.apache.ibatis.plugin.Interceptor: 拦截器可以用来实现二级缓存的逻辑。MyBatis允许用户自定义拦截器以扩展功能。
  • org.apache.ibatis.session.SqlSessionFactory: 在SqlSessionFactory的层面上配置二级缓存。

5.总结

MyBatis的一级缓存是默认开启的,它是基于SqlSession来实现的。每当我们创建一个新的SqlSession时,MyBatis都会创建一个新的BaseExecutor,这个BaseExecutor与一级缓存关联。一级缓存内部设计为一个没有容量限定的HashMap,存储了查询的结果集,以便于后续相同的查询可以直接从缓存中获取结果,而不必再次执行SQL语句到数据库中检索。但是需要注意的是,一级缓存的范围仅限于同一个SqlSession内。如果有多个SqlSession或者在分布式环境下,由于数据库写操作可能会引起脏数据问题,所以建议设定缓存级别为Statement。

而二级缓存提供了更广范围的缓存策略,它是基于namespace的,即多个mapper文件共享同一个二级缓存。与一级缓存不同的是,二级缓存需要手动开启,并且要求返回的POJO必须是可序列化的。MyBatis允许用户自定义Cache接口实现,并提供了多种缓存刷新策略如LRU(最近最少使用),FIFO(先进先出)等。二级缓存的生命周期、作用范围、失效策略等都是配置和使用时需要细致考虑的因素。

6.扩展

6.1 装饰器模式

一、概述:

动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

二、适用性:

1.在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

2.处理那些可以撤消的职责。

3.当不能采用生成子类的方法进行扩充时。

三、举例:

孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。

装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

6.2 适配器模式

一、概述

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

二、适用性

1.你想使用一个已经存在的类,而它的接口不符合你的需求。

2.你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口 可能不一定兼容的类)协同工作。

3.(仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行 子类化以匹配它们的接口。对象适配器可以适配它的父类接口。

三、举例

通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

适配器继承或依赖已有的对象,实现想要的目标接口。

7.实操

创建一个获取bean工具类

import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 *@Description  bean的工具类
 *@Author fei.chen
 *@Date bean的工具类 17:55
 */
@Component
public class ApplicationContextHolder implements ApplicationContextAware, DisposableBean {

    private static final Logger logger = LoggerFactory.getLogger(ApplicationContextHolder.class);

    private static ApplicationContext applicationContext;

    /**
     * 获取存储在静态变量中的 ApplicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        assertContextInjected();
        return applicationContext;
    }

    /**
     * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型
     *
     * @param name
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name) {
        assertContextInjected();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        assertContextInjected();
        return applicationContext.getBean(clazz);
    }

    /**
     * 实现 DisposableBean 接口,在 Context 关闭时清理静态变量
     *
     * @throws Exception
     */
    public void destroy() throws Exception {
        logger.debug("清除 SpringContext 中的 ApplicationContext: {}", applicationContext);
        applicationContext = null;
    }

    /**
     * 实现 ApplicationContextAware 接口,注入 Context 到静态变量中
     *
     * @param applicationContext
     * @throws BeansException
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHolder.applicationContext = applicationContext;
    }

    /**
     * 断言 Context 已经注入
     */
    private static void assertContextInjected() {
        Validate.validState(applicationContext != null, "applicationContext 属性未注入,请在 spring-context.xml 配置中定义 SpringContext");
    }
}

创建RedisTemplateConfiguration

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @Description RedisTemplate配置
 * @Author fei.chen
 * @Date 2024/4/15 17:54
 */
@Configuration
public class RedisTemplateConfiguration {

    /**
     * redisTemplate
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory
                                                               redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        //对于Null值不输出
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // 设置key和value的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // 设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        //afterPropertiesSet和init-method之间的执行顺序是afterPropertiesSet 先执行,init - method 后执行。
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

创建实现mybatisCache的实现类

import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Description MybatisRedisCache 缓存工具类
 *
 * docker exec -it 11053239f2b1 redis-cli
 * keys *
 * @Author fei.chen
 * @Date 2024/4/15 17:56
 */
@SuppressWarnings("all")
public class MybatisRedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final String id; // cache instance id
    private RedisTemplate redisTemplate;

    private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间

    public MybatisRedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }


    @Override
    public void putObject(Object key, Object value) {
        try {
            redisTemplate = getRedisTemplate();
            if (value != null) {
                redisTemplate.opsForValue().set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
            }
            logger.info("----------------------》放进了去了");
        } catch (Throwable t) {
            logger.error("Redis put failed", t);
        }


    }


    @Override
    public Object getObject(Object key) {
        try {
            redisTemplate = getRedisTemplate();
            logger.info("----------------------》拿出来了");
            return redisTemplate.opsForValue().get(key.toString());
        } catch (Throwable t) {
            logger.error("Redis get failed, fail over to db", t);
            return null;
        }
    }

    @Override
    public Object removeObject(Object key) {
        try {
            redisTemplate = getRedisTemplate();
            redisTemplate.delete(key.toString());
            logger.info("----------------------》removeObject");
        } catch (Throwable t) {
            logger.error("Redis remove failed", t);
        }
        return null;
    }


    @Override
    public void clear() {
        redisTemplate = getRedisTemplate();
        Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
        if (!CollectionUtils.isEmpty(keys)) {
            redisTemplate.delete(keys);
        }
        logger.info("----------------------》clear");
    }

    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
        }
        return redisTemplate;
    }
}

redis配置

  redis:
    host: localhost
    port: 6379
    password:
    lettuce:
      pool:
        #最大允许连接数
        max-active: 100
        #最小空闲连接数,最少准备5个可用连接在连接池候着
        min-idle: 5
        #最大空闲连接数,空闲连接超过10个后自动释放
        max-idle: 10
        #当连接池到达上限后,最多等待30秒尝试获取连接,超时报错
        max-wait: 30000
    timeout: 2000

开启二级缓存

注解开启:StateMapper找要实现二级缓存的一个mapper类配置:@CacheNamespace(implementation = MybatisRedisCache.class)

@CacheNamespace(implementation = MybatisRedisCache.class)
public interface StateMapper extends BaseMapper<StateEntity> {

    StateEntity findByIdCache(@Param("id") String id);

}

配置文件开启:

    <cache type="****.MybatisRedisCache"/>
    
     <select id="findByIdCache" resultMap="BaseResultMap" useCache="true">
        select * from t_state where id = #{id}
    </select>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

4.17作业

#include "double_link_list.h" node_p create_double_link_list() //创建双向链表 {node_p H(node_p)malloc(sizeof(node));if(HNULL){printf("空间申请失败\n");return NULL;}H->data0;H->priNULL;H->nextNULL;return H; } node_p create_node…

照片jpg格式小于50kb怎么弄?jpg压缩到指定大小

我们经常需要处理大量的图片&#xff0c;特别是在分享到社交媒体时&#xff0c;然而&#xff0c;图片文件的大小常常成为困扰我们的问题&#xff0c;尤其是当我们的设备存储空间有限时。有些平台甚至会需要将图片压缩到50kb大小&#xff0c;那么&#xff0c;如何有效地压缩图片…

学习Rust的第4天:常见编程概念

基于Steve Klabnik的《The Rust Programming Language》一书。昨天我们做了一个猜谜游戏 &#xff0c;今天我们将探讨常见的编程概念&#xff0c;例如&#xff1a; Variables 变量Constants 常数Shadowing 阴影Data Types 数据类型Functions 功能 Variables 变量 In layman ter…

HackMyVM-suidy

目录 信息收集 arp nmap WEB web信息收集 gobuster 目录批量查看 hydra ssh连接 提权 系统信息收集 提权 信息收集 arp ┌─[rootparrot]─[~/HackMyVM] └──╼ #arp-scan -l Interface: enp0s3, type: EN10MB, MAC: 08:00:27:16:3d:f8, IPv4: 192.168.9.115 St…

【网络通信基础】网络中的常见基本概念

目录 一、IP地址 二、端口号 三、协议 四、五元组 五、协议分层 1. OSI 模型 2. TCP/CP五层&#xff08;或四层&#xff09;模型 3. 网络设备所在分层 六、封装和分用 封装&#xff08;Encapsulation&#xff09; 分用&#xff08;Multiplexing&#xff09; 一、IP…

多目标环形粒子群算法和多目标遗传算法跑MOCEC2020(24个多目标测试函数,matlab代码,多个评价指标)

本号从现在起可以定制使用评估次数改进单目标群体算法&#xff0c;需要的私&#xff0c;价格贵&#xff0c;质量高。 目录&#xff1a; 一、多目标环形粒子群算法MO_Ring_PSO_SCD 二、多目标遗传算法NSGAII 三、MOCEC2020的24个多目标测试函数 四、实验结果 五、代码获取…

C语言进阶课程学习记录-函数指针的阅读

C语言进阶课程学习记录-函数指针的阅读 5个标识符含义解析技巧 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 5个标识符含义解析 int (*p1) (int* , int (*f) ( int* ) );定义了指针p1,指向函数&#…

javaWeb智能医疗管理系统

简介 在当今快节奏的生活中&#xff0c;智能医疗系统的崛起为医疗行业带来了一场革命性的变革。基于JavaWeb技术开发的智能医疗管理系统&#xff0c;不仅为医疗机构提供了高效、精准的管理工具&#xff0c;也为患者提供了更便捷、更个性化的医疗服务。本文将介绍一个基于SSM&a…

树莓集团产业生态建设之特色产业服务:人才项目转化中心

树莓集团在产业生态建设中&#xff0c;积极输出特色产业服务——人才项目转化中心。该中心依托数字产业园致力于推动创新创业工作&#xff0c;通过链接产业人才聚集地与树莓认证的导师库体系&#xff0c;为人才及相关课题项目提供全方位的服务。 树莓集团人才项目转化中心以人…

Python学习(四)文件操作

文件操作 想想我们平常对文件的基本操作&#xff0c;大概可以分为三个步骤(简称文件操作三步走): ① 打开文件 ② 读写文件 ③ 关闭文件 注意:可以只打开和关闭文件&#xff0c;不进行任何读写 在Python&#xff0c;使用open函数&#xff0c;可以打开一个已经存在的文件&…

IP爬虫代理服务器是什么以及为什么使用爬虫代理?

在网络抓取领域&#xff0c;爬虫代理发挥着关键作用。 但它们到底是什么&#xff1f; 从本质上讲&#xff0c;爬虫代理是位于网络抓取工具和目标网站之间的中间服务器。 该中间服务器充当盾牌&#xff0c;提供匿名性&#xff0c;并允许您访问网站并提取数据&#xff0c;而无需透…

IDEA配置Maven环境

黑马程序员JavaWeb开发教程 文章目录 如果当前有已经打开项目的话&#xff0c;File -> Close Project 到以下页面之后选择 Customize -> All settings… 配置maven的安装目录&#xff0c;maven的配置文件&#xff0c;maven的本地仓库&#xff08;修改完成之后一定要先…

mybatis进阶篇-执行CRUD操作-typeAliases别名-接口绑定

目录结构 所需jar包 https://download.csdn.net/download/weixin_44201223/89160447?spm1003.2166.3001.6637.1 1.创建数据表&#xff08;book&#xff09; # 创建book表 create table book(id int auto_increment primary key,name varchar(255) ,price double ,num int )…

Linux OpenSSH最新版9.7p1升级操作详细教程

原创声明&#xff1a;非本人许可&#xff0c;谢绝转载&#xff01; 1.背景说明 前几天与朋友闲聊中得知他朋友圈有服务器因OpenSSH漏洞遭受攻击的事情&#xff0c;OpenSSH重要性这里就不废话了&#xff0c;在网上一查&#xff0c;公布的漏洞还真不少&#xff0c;其中还有不少…

RK3588平台开发系列讲解(PCIe开发篇2)

根据原理图填写DTS 原理图是基于IO信号的视⻆来描述硬件&#xff0c;IO信号是跟PHY的index强相关的&#xff0c;前⾯提到RK3588的controller和PHY的index可能不⼀致&#xff0c;所以看原理图的时候需要特别注意这⼀点。这⾥给出⼀些填写建议&#xff0c;并通过⽰例说明如何将原…

socket通信基础讲解及示例-C

socket通信之C篇 服务端与客户端简介 socket通信服务端与客户端通信模型通信实战server&#xff08;服务端&#xff09;创建client&#xff08;客户端&#xff09;创建 函数详解创建套接字 socket绑定端口bind进入监听状态listen获取客户端连接请求accept接收网络数据read发送数…

音素与视素(Viseme)

什么是视素&#xff08;视位&#xff09; 音素(Phoneme)&#xff0c;是人类语言中能够区别意义的最小声音单位。视素(Viseme)&#xff0c;是指与某一音素相对应的嘴、舌头、下腭等可视发音器官所处的状态。Viseme是MPEG-4 标准提出来的概念。 有时Viseme也翻译为视位。下面会…

JavaEE初阶Day 10:多线程(8)

目录 Day 10&#xff1a;多线程&#xff08;8&#xff09;单例模式阻塞队列1. 生产者消费者模型1.1 生产者消费者模型解耦合1.2 生产者消费者模型削峰填谷 2. 生产者消费者代码3. 阻塞队列实现 Day 10&#xff1a;多线程&#xff08;8&#xff09; 单例模式 单例模式&#xf…

springboot中mongodb连接池配置-源码分析

yml下spring.data.mongodb 以前mysql等在spring.xxx下配置&#xff0c;现在springboot新版本&#xff08;小编3.2.3&#xff09;在spring.data.xxx下了&#xff0c;如下所示&#xff0c;mongodb的配置在spring.data.mongodb下&#xff1a; 连接池相关参数配置-源码分析 拼接在…

vue3 删除对象中的属性,可以使用js里的delete,但需注意ts定义对象类型!

如上如&#xff0c;当使用delete 删除stateData中的属性时&#xff0c; 报错&#xff0c;意思为 TypeScript 错误“‘delete’ 运算符的操作数必须是可选的 什么原因呢&#xff1f;是因为我偷懒 缺少了ts定义类型 方法一&#xff1a; &#xff08;不推荐&#xff09; delete …