【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>
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞四海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值