本地声明式缓存

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());
    }
 
}
相关推荐
草履虫建模2 小时前
Redis:高性能内存数据库与缓存利器
java·数据库·spring boot·redis·分布式·mysql·缓存
程序猿ZhangSir4 小时前
Redis 缓存进阶篇,缓存真实数据和缓存文件指针最佳实现?如何选择?
数据库·redis·缓存
段帅龙呀12 小时前
Redis构建缓存服务器
服务器·redis·缓存
夜斗小神社1 天前
【黑马点评】(二)缓存
缓存
Hello.Reader1 天前
Redis 延迟监控深度指南
数据库·redis·缓存
Hello.Reader2 天前
Redis 延迟排查与优化全攻略
数据库·redis·缓存
在肯德基吃麻辣烫2 天前
《Redis》缓存与分布式锁
redis·分布式·缓存
先睡2 天前
Redis的缓存击穿和缓存雪崩
redis·spring·缓存
CodeWithMe3 天前
【Note】《深入理解Linux内核》 Chapter 15 :深入理解 Linux 页缓存
linux·spring·缓存
大春儿的试验田3 天前
高并发收藏功能设计:Redis异步同步与定时补偿机制详解
java·数据库·redis·学习·缓存