目的:主要是实现查询效率上的优化,减少数据库方面的压力,提升系统的性能

L1:一级缓存,主要是本地内存缓存,不需要网络传输,实时性很高,适合存储数据量少的数据,不可以持久化存储。

L2:二级缓存,独立于服务器/程序之外的外部缓存,适合存储数据量大大一点的数据,可持久化存储,性能稍慢于一级缓存,如redis,支持集群部署,多节点共享数据。

1.maven依赖

对于2.7.x及以上版本的无需指定caffeine版本号,spring会自动去引入兼容版本。
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>

2.开始配置

2.1 添加@EnableCaching注解
在启动类上添加@EnableCaching开关。
@EnableCaching
@SpringBootApplication
public class SystemApplication {
    .......
}
2.2 创建CacheConfig类,注入到spring管理中
一个是Caffeine本地缓存管理器,一个是多级缓存管理器。
@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                // 初始化容量
                .initialCapacity(100)
                // 最大容量
                .maximumSize(1000)
                // 过期时间
                .expireAfterWrite(3600, TimeUnit.SECONDS)
                .recordStats());
        return cacheManager;
    }


    * 一二级缓存管理器
     * 
     * @param caffeineCacheManager Caffeine缓存管理
     * @param redisCacheUtil       redis工具类
     * @return
     */
    @Primary
    @Bean
    public CacheManager levelCacheManager(@Qualifier(value = "caffeineCacheManager") CacheManager caffeineCacheManager,
            @Autowired RedisCacheUtil redisCacheUtil) {
        return new LevelCacheManager(caffeineCacheManager, redisCacheUtil);
    }


}
2.3 创建一二级缓存管理器
里面同时实现了对每个key值自定义过期时间,默认是设置通用过期时长。
public class LevelCacheManager implements CacheManager {

    private final CaffeineCacheManager caffeineCacheManager;
    private final RedisCacheUtil redisCacheUtil;

    public LevelCacheManager(CacheManager caffeineCacheManager, RedisCacheUtil redisCacheUtil) {
        this.caffeineCacheManager = (CaffeineCacheManager) caffeineCacheManager;
        this.redisCacheUtil = redisCacheUtil;
    }

    @Override
    public Cache getCache(String name) {
        CaffeineCache caffeineCache = parseName(name);
        return new LevelCache(name, caffeineCache, redisCacheUtil);
    }

    @Override
    public Collection<String> getCacheNames() {
        return Collections.emptyList();
    }

    private CaffeineCache parseName(String name) {
        long duration = 0; // 默认60分钟
        if (name.contains("#")) {
            // 解析名称中的有效期信息
            String[] parts = name.split("#");
            name = parts[0];
            if (parts.length > 1) {
                try {
                    duration = Long.parseLong(parts[1]);
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                }
            }
        }
        com.github.benmanes.caffeine.cache.Cache<Object, Object> build = Caffeine.newBuilder()
                // 初始化容量
                .initialCapacity(100)
                // 最大容量
                .maximumSize(1000)
                // 过期时间
                .expireAfterWrite(duration, TimeUnit.SECONDS).recordStats().build();
        CaffeineCache caffeineCache = (CaffeineCache) caffeineCacheManager.getCache(name);
        CaffeineCache caffeineCache2 = new CaffeineCache(name, build);
        if (duration > 0) {
            redisCacheUtil.setEXPIRE_TIME(duration);
            return caffeineCache2;
        }
        redisCacheUtil.setEXPIRE_TIME(3600L);
        return caffeineCache;
    }
2.4 自定义一,二级缓存操作类
实现一二级缓存顺序读取,先查一级再查二级缓存,序列化方式保持一致。
public class LevelCache implements Cache {

    private final RedisCacheUtil redisCacheUtil;
    private final String name;
    private final CaffeineCache caffeineCache;


    public LevelCache(String name, CaffeineCache caffeineCache, RedisCacheUtil redisCacheUtil){
        this.name=name;
        this.caffeineCache = caffeineCache;
        this.redisCacheUtil = redisCacheUtil;
    }

    @Override
    public String getName() {
        return name;
    }


    /**
     * 仅当缓存中不存在指定键时,才将键值对存入缓存。若键已存在,不会进行覆盖操作,而是返回缓存中现有的值。
     * @param key
     * @param value
     * @return
     */
    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        ValueWrapper existingValue = this.get(key);
        if (existingValue == null) {
            caffeineCache.put(key, value);
        }
        Object obj = redisCacheUtil.get(String.valueOf(key));
        if (obj==null){
            redisCacheUtil.set(String.valueOf(key),value,redisCacheUtil.getEXPIRE_TIME());
        }
        return this.get(key);
    }

    @Override
    public Object getNativeCache() {
        return null;
    }

    /**
     * 获取缓存中的数据
     * @param key
     * @return
     */
    @Override
    public ValueWrapper get(Object key) {
        ValueWrapper valueWrapper = caffeineCache.get(key);
        if (valueWrapper != null){
            return valueWrapper;
        }
        Object value= redisCacheUtil.get(String.valueOf(key));
        if(value!=null){
            caffeineCache.put(key, value);
            return this.get(key);
        }
        return null;
    }

    /**
     * 获取缓存中数据,具体对象
     * @param key
     * @param type
     * @return
     * @param <T>
     */
    @Override
    public <T> T get(Object key, Class<T> type) {
        T value = caffeineCache.get(key, type);
        if (value != null){
            return value;
        }
        Object rValue = redisCacheUtil.get(String.valueOf(key));
        if (rValue != null){
            caffeineCache.put(key,rValue);
            return (T) rValue;
        }
        return null;
    }


    /**
     * 如果一二级缓存中都不存在情况下,使用自定义的valueLoader加载数据,并缓存到一级缓存和二级缓存中
     * @param key
     * @param valueLoader
     * @return
     * @param <T>
     */
    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        ValueWrapper value=this.get(key);
        if (value!=null) {
            return (T) value.get();
        }else{
            Object obj;
            try {
                obj = valueLoader.call();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            caffeineCache.put(key,obj);
            redisCacheUtil.set(String.valueOf(key),obj,redisCacheUtil.getEXPIRE_TIME());
            return (T)obj;
        }
    }

    /**
     * 填入一二级缓存
     * @param key
     * @param value
     */
    @Override
    public void put(Object key, Object value) {
        caffeineCache.put(key,value);
        redisCacheUtil.set(String.valueOf(key),value,redisCacheUtil.getEXPIRE_TIME());
    }


    /**
     * 移除指定key值的一二级缓存
     * @param key
     */
    @Override
    public void evict(Object key) {
        caffeineCache.evict(key);
        redisCacheUtil.del(String.valueOf(key));
    }


    /**
     * 删除所有一二级缓存
     */
    @Override
    public void clear() {
        caffeineCache.clear();
        redisCacheUtil.delByPrefix(CacheKeyGenerator.KEY_PREFIX);
    }
}
2.5 自定义key值前缀工具类
在配合方法参数时,可以自己拼接前缀在key前面,实现统一管理。
public class CacheKeyGenerator {

    public static final String KEY_PREFIX = "levelCache:";
    public static final String USER_CACHE = "userCaches";

    public static String generateKey(String name) {
        return KEY_PREFIX + name;
    }
}
2.6 使用注解方式实现案例
    @Cacheable(cacheNames = CacheKeyGenerator.USER_CACHE+"#10",
            key= "T(com.makingTeam.config.cache.CacheKeyGenerator).generateKey(#name)")
    public Object login(String name, String pwd) {
        Map<Object, Object> objectObjectMap = new HashMap<>();
        objectObjectMap.put(name, pwd);
        log.info("login-----查找数据库");
        return objectObjectMap;
    }

  @CachePut(cacheNames = CacheKeyGenerator.USER_CACHE+"#30",
            key= "T(com.makingTeam.config.cache.CacheKeyGenerator).generateKey(#name)")
    public Object updateLogin(String name, String pwd) {
        Map<Object, Object> objectObjectMap = new HashMap<>();
        objectObjectMap.put(name, pwd);
        log.info("updateLogin----查找数据库");
        return objectObjectMap;
    }

    @CacheEvict(cacheNames = CacheKeyGenerator.USER_CACHE,
            key= "T(com.makingTeam.config.cache.CacheKeyGenerator).generateKey(#name)")
    public void del(String name) {
        log.info("del----删除");
    }

完结!!!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐