SpringBoot+Caffeine+Redis声明式缓存

目录

【博客目的】

【应用场景】

【相关知识】

【代码实践】

引入组件

配置文件

配置类

启动类

业务层

实体类

接口层


【博客目的】

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

【应用场景】

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

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

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

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

【相关知识】

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

@Cacheable注解详解:SpringBoot 缓存之 @Cacheable 详细介绍_zl1zl2zl3的博客-CSDN博客_@cacheable一、简介1、缓存介绍Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个https://blog.csdn.net/zl1zl2zl3/article/details/110987968

组合拳:

Spring系列缓存注解@Cacheable @CacheEvit @CachePut 使用姿势介绍 - 简书SpringBoot系列缓存注解@Cacheable @CacheEvit @CachePut使用姿势介绍 Spring在3.1版本,就提供了一条基于注解的缓存策略,实际使用...https://www.jianshu.com/p/29c1916e0df3

【代码实践】

废话不多讲,直接开撸。

引入组件

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

        <!-- 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不用。

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 配置类:

package com.xxx.xxx.cache;

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。

PS:还在研究,理解如有错误,欢迎指正!

启动类

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

package com.xxx.xxx;

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

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

业务层

CacheTestService 接口:

package com.xxx.xxx.service.base;

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 实现类

package com.xxx.xxx.service.base.impl;

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

}

实体类

package com.xxx.xxx.entity;

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;
}

接口层

package com.xxx.xxx.controller;

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

完成!成果图我就不贴了,感兴趣可以试一下!

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值