本地声明式缓存

SpringBoot+Caffeine+Redis声明式缓存

最近接到一个项目,里面同时整合了Caffeine和Redis。

对于像验证码,或者对用户操作做一些限制的缓存,还有分布式锁等等操作就利用redis来缓存,

对于一些热点数据,为了降低数据库查询频率,就使用Caffeine本地缓存来实现。

至于为什么这么做?这个问题问得好!下次别问了!

记录一个项目中同时整合了Caffeine和Redis时,怎么使用@Cacheable这样的注解,优雅地实现缓存。

相关知识

对相关原理和注解@Cacheable/@CachePut/@CacheEvit不熟练的同学请移步相关文章,能很好地理解。

代码实践】

废话不多讲,直接开撸。

引入组件

先创建一个springboot项目,再pom文件中导入以下依赖:

java 复制代码
  <!-- Redis缓存 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<version>2.6.1</version>
		</dependency>
 
		<!-- 咖啡因缓存 -->
		<dependency>
			<groupId>com.github.ben-manes.caffeine</groupId>
			<artifactId>caffeine</artifactId>
			<version>2.9.1</version>
		</dependency>

配置文件

接下来就是针对两个缓存组件的配置

在application.yml中,需要对redis的连接信息做一些基础配置,Caffeine不用。

java 复制代码
spring:
   redis:
    # Redis服务器地址
    host: 127.0.0.1
    # Redis数据库索引(默认为0)
    database: 1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password: pas
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 0
    # 连接超时时间(毫秒)
    timeout: 3000ms

配置类

CacheConfig 配置类:

java 复制代码
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
 
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
 
 
@Configuration
public class CacheConfig {
    @Bean("caffeineCacheManager")
    @Primary
    public CacheManager cacheManager(){
        SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
        ArrayList<Cache> caches = new ArrayList<>();
        caches.add(new CaffeineCache("test1",
                Caffeine.newBuilder()
                        .expireAfterWrite(100, TimeUnit.SECONDS)
                        .recordStats()
                        .maximumSize(Integer.MAX_VALUE)
                        .removalListener((key, value, cause) -> {
                            System.out.println("");
                        }).build()));
 
        caches.add(new CaffeineCache("test2",
                Caffeine.newBuilder()
                        .expireAfterWrite(100, TimeUnit.SECONDS)
                        .recordStats()
                        .maximumSize(Integer.MAX_VALUE)
                        .removalListener((key, value, cause) -> {
                            System.out.println("");
                        }).build()));
        simpleCacheManager.setCaches(caches);
        return simpleCacheManager;
    }
 
 
    @Bean("redisCacheManager")
    public CacheManager redisCacheManager(RedisConnectionFactory factory){
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 设置缓存的默认过期时间
                .entryTtl(Duration.ofSeconds(180))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()))
                // 不缓存空值
                .disableCachingNullValues();
 
        return RedisCacheManager
                .builder(factory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }
}

之所以要配置两个Manager的原因简单说一下。

我们在使用@Cacheable注解时,在Caffeine中,Spring底层是通过@Cacheable的cacheManager属性的值去找对应的CacheManger中名为value属性值的缓存容器实例;

而在Redist中又不一样,整个Redis就是一个缓存容器,所以是通过CacheManager的属性值去调用对应的Redis缓存容器实例,而此时的value属性值和key属性的值,一起组成了redis的key。

启动类

启动类上添加注解@EnableCaching开启自动缓存支持。

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@SpringBootApplication
@EnableCaching  //开启自动缓存
@EnableAsync	//开启异步支持
@EnableTransactionManagement // 开启事务支持
public class ProjectApplication {
	public static void main(String[] args) {
		SpringApplication.run(ProjectApplication.class, args);
	}
}

基本上就算是配置结束了,下面可以直接使用了。

实体类

java 复制代码
import lombok.Data;
import lombok.experimental.Accessors;
 
import java.io.Serializable;
import java.util.Date;
 
/**
 * @version 1.0.0
 * @Author DG
 * @Date 2022/1/6 15:13
 */
@Data
@Accessors(chain = true) // 链式编程
public class Person implements Serializable {
    private Long id;
 
    private String name;
 
    private int age;
 
    private String[] hobby;
 
    private String address;
 
    private Date createTime;
}

控制层

java 复制代码
import com.xxx.xxx.entity.Person;
import com.xxx.xxx.service.base.CacheService;
import com.xxx.xxx.service.base.CacheTestService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
 
/**
 * @version 1.0.0
 * @Author DG
 * @Date 2022/1/6 15:23
 */
@RestController
@Api(value = "测试功能", tags = "功能测试")
public class CacheController {
    @Resource
    private CacheTestService cacheTestService;
   
 
    @GetMapping("/get")
    @ApiOperation(value = "这是测试一级缓存和二级缓存同时使用的控制器", tags = "如果入参为奇数走redis,如果入参为偶数走caffeine")
    @ApiImplicitParam(name = "id", value = "对象ID,就是一个标记而已", dataType = "Long", dataTypeClass = Long.class, defaultValue = "0", example = "0")
    public List<Person> selectPerson(Long id){
        Person cache1;
        Person cache2;
        if (id % 2 == 0) {
            cache1 = cacheTestService.testCaffeineCache1(id);
            cache2 = cacheTestService.testRedisCache1(id);
        } else {
            cache1 = cacheTestService.testCaffeineCache2(id);
            cache2 = cacheTestService.testRedisCache2(id);
        }
        return Arrays.asList(cache1, cache2);
    }
}

业务层

CacheTestService 接口:

java 复制代码
import com.xxx.xxx.entity.Person;
 
public interface CacheTestService {
 
    /**
     * 测试caffeine缓存1
     * @return
     */
    Person testCaffeineCache1(Long id);
 
    /**
     * 测试caffeine缓存2
     * @return
     */
    Person testCaffeineCache2(Long id);
 
    /**
     * 测试redis缓存1
     * @return
     */
    Person testRedisCache1(Long id);
 
    /**
     * 测试redis缓存2
     * @return
     */
    Person testRedisCache2(Long id);
}

CacheTestServiceImpl 实现类

java 复制代码
import com.xxx.xxx.entity.Person;
import com.xxx.xxx.service.base.CacheTestService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
 
import java.util.Date;
 
@Service
public class CacheTestServiceImpl implements CacheTestService {
 
    @Override
    @Cacheable(value = "test1", key = "#id", cacheManager = "caffeineCacheManager")
    public Person testCaffeineCache1(Long id) {
        // 模拟数据库查询并返回
        return new Person()
                .setId(id)
                .setAge(18)
                .setHobby(new String[]{"java"})
                .setAddress("松下问童子")
                .setName("caffeineCache1")
                .setCreateTime(new Date());
    }
 
    @Override
    @Cacheable(value = "test2", key = "#id", cacheManager = "caffeineCacheManager")
    public Person testCaffeineCache2(Long id) {
        // 模拟数据库查询并返回
        return new Person()
                .setId(id)
                .setAge(19)
                .setHobby(new String[]{"C#"})
                .setAddress("言师采药去")
                .setName("caffeineCache2")
                .setCreateTime(new Date());
    }
 
    @Override
    @Cacheable(value = "test1", key = "#id", cacheManager = "redisCacheManager")
    public Person testRedisCache1(Long id) {
        // 模拟数据库查询并返回
        return new Person()
                .setId(id)
                .setAge(20)
                .setHobby(new String[]{"Python"})
                .setAddress("只在此山中")
                .setName("redisCache1")
                .setCreateTime(new Date());
    }
 
    @Override
    @Cacheable(value = "test2", key = "#id", cacheManager = "redisCacheManager")
    public Person testRedisCache2(Long id) {
        // 模拟数据库查询并返回
        return new Person()
                .setId(id)
                .setAge(21)
                .setHobby(new String[]{"Go"})
                .setAddress("云深不知处")
                .setName("redisCache2")
                .setCreateTime(new Date());
    }
 
}
相关推荐
材料苦逼不会梦到计算机白富美5 小时前
golang分布式缓存项目 Day 1
分布式·缓存·golang
Java 第一深情6 小时前
高性能分布式缓存Redis-数据管理与性能提升之道
redis·分布式·缓存
HBryce246 小时前
缓存-基础概念
java·缓存
想要打 Acm 的小周同学呀13 小时前
LRU缓存算法
java·算法·缓存
hlsd#13 小时前
go 集成go-redis 缓存操作
redis·缓存·golang
镰刀出海13 小时前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试
奶糖趣多多15 小时前
Redis知识点
数据库·redis·缓存
CoderIsArt16 小时前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存
ketil2721 小时前
Redis - String 字符串
数据库·redis·缓存
生命几十年3万天1 天前
redis时间优化
数据库·redis·缓存