目录

一、简介

二、SpringBoot 集成 Caffeine、Redis实现双重缓存


一、简介

在上一篇文章中,我们介绍了直接引入 Caffeine 依赖,然后使用 Caffeine方法与Redis实现了双重缓存,除了这种方式外,我们还可以引入 Caffeine 和 Spring Cache 依赖,这样我们可以使用 SpringCache 注解方法实现本地缓存。

下面我们引入 Caffeine 和 Spring Cache 依赖,看下如何集成Redis、Caffeine实现多级缓存。

二、SpringBoot 集成 Caffeine、Redis实现双重缓存

我们可以将热点数据存入本地缓存,作为一级缓存,将非热点数据放redis缓存,作为二级缓存,减少Redis的查询压力。

使用流程大致如下:

  • 首先从一级缓存(caffeine-本地应用内)中查找数据;
  • 如果没有的话,则从二级缓存(redis-内存)中查找数据;
  • 如果还是没有的话,再从数据库(数据库-磁盘)中查找数据;

下面来看具体的代码实现。

(一)、Maven 引入相关依赖

<dependencies><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--caffeine--><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId></dependency><!--spring cache--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.72</version></dependency><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

(二)、Redis相关配置文件

spring:redis:host: 127.0.0.1port: 6379password:jedis:pool:max-active: 8max-wait: -1max-idle: 500min-idle: 0lettuce:shutdown-timeout: 0

(三)、Caffeine自动配置类

/*** @Description: Caffeine自动配置类*/
//自动配置功能
@Configuration
//开启缓存功能
@EnableCaching
public class CaffeineCacheConfig {@Bean@Primarypublic CacheManager caffeineCacheManager() {SimpleCacheManager cacheManager = new SimpleCacheManager();List<CaffeineCache> caches = CaffeineCacheInitializer.initCaffeineCache();if (CollectionUtils.isEmpty(caches)) {return cacheManager;}cacheManager.setCaches(caches);return cacheManager;}}/*** @Description: CaffeineCache初始化器*/
public class CaffeineCacheInitializer {public static List<CaffeineCache> initCaffeineCache() {List<CaffeineCache> caffeineCacheList = new ArrayList<>();CaffeineCache userCache = new CaffeineCache(CacheKey.USER_CACHE_KEY, Caffeine.newBuilder().recordStats().expireAfterWrite(5, TimeUnit.SECONDS).maximumSize(100).build());caffeineCacheList.add(userCache);//将所有需要定义的CaffeineCache添加到容器中//....return caffeineCacheList;}
}/*** @Description: 缓存Key常量,统一维护*/
public class CacheKey {public static final String USER_CACHE_KEY = "userCache";}

如果使用了多个cahce,比如redis、caffeine等,必须指定某一个CacheManage为@Primary,在@Cacheable注解中没指定 cacheManager 的话,则使用标记为primary的那个。

(四)、Redis缓存配置类

@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();//关联template.setConnectionFactory(factory);//设置key的序列化方式
//        template.setKeySerializer();//设置value的序列化方式
//        template.setValueSerializer();return template;}
}

(五)、定义实体对象

public class User implements Serializable {private String id;private String name;public User(String id, String name) {this.id = id;this.name = name;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}
}

(六)、定义Service接口类

public interface UserService {User add(User user);User getById(String id);User update(User user);void deleteById(String id);
}

(七)、定义Service接口实现类

import com.alibaba.fastjson.JSON;
import com.wsh.springboot_caffeine2.constant.CacheKey;
import com.wsh.springboot_caffeine2.entity.User;
import com.wsh.springboot_caffeine2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.TimeUnit;@Service
public class UserServiceImpl implements UserService {/*** 模拟数据库存储数据*/private static HashMap<String, User> userMap = new HashMap<>();private final RedisTemplate<String, Object> redisTemplate;@Autowiredpublic UserServiceImpl(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}static {userMap.put("1", new User("1", "zhangsan"));userMap.put("2", new User("2", "lisi"));userMap.put("3", new User("3", "wangwu"));userMap.put("4", new User("4", "zhaoliu"));}@Override// 1.保存Caffeine缓存  注意必须返回User对象出去,如果是void的话,Caffeine并不能帮我们存入缓存中@CachePut(value = CacheKey.USER_CACHE_KEY, key = "#user.id")public User add(User user) {// 2.保存redis缓存redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);// 3.保存数据库(模拟)userMap.put(user.getId(), user);return user;}@Override// 1.先从Caffeine缓存中读取@Cacheable(value = CacheKey.USER_CACHE_KEY, key = "#id", sync = true)public User getById(String id) {// 2.如果缓存中不存在,则从Redis缓存中查找String jsonString = (String) redisTemplate.opsForValue().get(id);User user = JSON.parseObject(jsonString, User.class);if (Objects.nonNull(user)) {System.out.println("从Redis中查询到数据...");return user;}// 3.如果Redis缓存中不存在,则从数据库中查询user = userMap.get(id);if (Objects.nonNull(user)) {// 保存Redis缓存,20s后过期redisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user), 20, TimeUnit.SECONDS);}System.out.println("从数据库中查询到数据...");return user;}@Override//1.更新Caffeine缓存@CachePut(value = CacheKey.USER_CACHE_KEY, key = "#user.id")public User update(User user) {User oldUser = userMap.get(user.getId());oldUser.setName(user.getName());// 2.更新数据库userMap.put(oldUser.getId(), oldUser);// 3.更新Redis数据库redisTemplate.opsForValue().set(oldUser.getId(), JSON.toJSONString(oldUser), 20, TimeUnit.SECONDS);return oldUser;}@Override//1.删除Caffeine缓存@CacheEvict(value = CacheKey.USER_CACHE_KEY, key = "#id")public void deleteById(String id) {// 2.删除数据库userMap.remove(id);// 3.删除Redis缓存redisTemplate.delete(id);}}

Spring Cache方面的注解主要有以下5个:

  • @Cacheable :触发缓存入口(这里一般放在创建和获取的方法上,@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存);
  • @CacheEvict :触发缓存的eviction(用于删除的方法上);
  • @CachePut :更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行);
  • @Caching :将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解);
  • @CacheConfig :在类级别设置一些缓存相关的共同配置(与其它缓存配合使用);

总结一下@Cacheable@CachePut的区别:

  • @Cacheable:它的注解的方法是否被执行取决于Cacheable中的条件,方法很多时候都可能不被执行
  • @CachePut:这个注解不会影响方法的执行,也就是说无论它配置的条件是什么,方法都会被执行,更多的时候是被用到修改上;

注意:标注了@Cacheable、@CachePut的方法,如果方法的返回类型是void或者方法返回值是null,那么将会把null值存入缓存中,当插入了数据后,再执行查询,会查出null的,必须注意这一点。因此推荐方法的返回值类型是要存进缓存的值类型。

(八)、单元测试

  • 查询
@Test
void testSelect() {System.out.println("=============第一次查询:=============");User user = userService.getById("1");System.out.println(user);System.out.println("=============第二次查询:=============");user = userService.getById("1");System.out.println(user);System.out.println("=============第三次查询:=============");try {//休眠7s后再次查询,主要是模拟Caffeine过期TimeUnit.SECONDS.sleep(7);user = userService.getById("1");System.out.println(user);} catch (InterruptedException e) {e.printStackTrace();}
}

执行结果如下:

=============第一次查询:=============
从数据库中查询到数据...
User{id=1, name='zhangsan'}
=============第二次查询:=============
User{id=1, name='zhangsan'}
=============第三次查询:=============
从Redis中查询到数据...
User{id=1, name='zhangsan'}

可以看到,第一次查询,因为Caffeine和Redis都没有对应的数据,所以从数据库中查询,然后第二次查询,Caffeine中有数据了,所以从Caffeine本地缓存中直接获取,休眠一段时间,使Caffeine过期之后,进行第三次查询,这次则从Redis中获取,因为Redis我们设置的过期时间比Caffeine长。

  • 新增
@Test
void testAdd() {userService.add(new User("5", "田七"));System.out.println("=============第一次查询:=============");User user = userService.getById("5");System.out.println(user);System.out.println("=============第二次查询:=============");try {//休眠7s后再次查询,主要是模拟Caffeine过期TimeUnit.SECONDS.sleep(7);user = userService.getById("5");System.out.println(user);} catch (InterruptedException e) {e.printStackTrace();}
}

执行结果如下:

=============第一次查询:=============
User{id=5, name='田七'}
=============第二次查询:=============
从Redis中查询到数据...
User{id=5, name='田七'}

可以看到,第一次查询,因为新增完之后,往Caffeine中存放了数据,所以第一次是从Caffeine查询的,休眠一段时间,使Caffeine过期之后,进行第二次查询,这次则从Redis中获取,因为Redis我们设置的过期时间比Caffeine长。

  • 修改
@Test
void testUpdate() {userService.update(new User("1", "zs"));System.out.println("=============第一次查询:=============");User user = userService.getById("1");System.out.println(user);System.out.println("=============第二次查询:=============");try {//休眠7s后再次查询,主要是模拟Caffeine过期TimeUnit.SECONDS.sleep(7);user = userService.getById("1");System.out.println(user);} catch (InterruptedException e) {e.printStackTrace();}
}

执行结果如下:

=============第一次查询:=============
User{id=1, name='zs'}
=============第二次查询:=============
从Redis中查询到数据...
User{id=1, name='zs'}

可以看到,第一次查询,因为修改完之后,更新了Caffeine中的数据,所以第一次是从Caffeine查询的,休眠一段时间,使Caffeine过期之后,进行第二次查询,这次则从Redis中获取,因为Redis我们设置的过期时间比Caffeine长。

  • 删除
@Test
void testDelete() {System.out.println("=============第一次查询:=============");User user = userService.getById("1");System.out.println(user);userService.deleteById("1");System.out.println("=============第二次查询:=============");User user2 = userService.getById("1");System.out.println(user2);
}

执行结果如下:

=============第一次查询:=============
从数据库中查询到数据...
User{id=1, name='zhangsan'}
=============第二次查询:=============
从数据库中查询到数据...
null

可以看到,第一次查询,因为删除完之后,同时也删除了Caffeine和Redis中的数据,所以第一次是从数据库中查询的,接着进行第二次查询也是从数据库进行查询,只是查询不到对应的数据。

SpringBoot 集成 Caffeine、Redis实现双重缓存方式(二)相关推荐

  1. SpringBoot 集成 Caffeine(咖啡因)最优秀的本地缓存

    SpringBoot 集成 Caffeine(咖啡因)最优秀的本地缓存 本地缓存 为什么用Caffeine做本地缓存 SpringBoot2.0+如何集成 Caffeine 引入依赖 开启缓存 容器配 ...

  2. SpringBoot 集成 layering-cache 实现两级缓存调研与实践

    前言 对于系统查多改少的数据,可以通过缓存来提升系统的访问性能.一般情况下我们会采用 Redis ,但是如果仅仅依赖 Redis 很容易出现缓存雪崩的情况.为了防止缓存雪崩可以通过 Redis 高可用 ...

  3. SpringBoot+Caffeine+Redis声明式缓存

    目录 [博客目的] [应用场景] [相关知识] [代码实践] 引入组件 配置文件 配置类 启动类 业务层 实体类 接口层 [博客目的] 记录一个项目中同时整合了Caffeine和Redis时,怎么使用 ...

  4. Springboot集成Shiro+Redis后,@Transactional注解不起作用

    为什么80%的码农都做不了架构师?>>>    使用Springboot构建 mybatis+Shiro+Redis+Druid 的前后端分离web项目, 具体可以参考博客https ...

  5. springboot整合ehcache+redis实现双缓存

    在一些对并发业务要求较高的场景下,对页面数据的响应是个急需解决的问题,对后端来说,ehcache+redis实现双缓存是解决这一问题的不错思路,而且在不少的电商项目中得到了很好的验证,但我在网上搜寻资 ...

  6. 【SpringBoot集成ElasticSearch 01】2️⃣ 种方式的高级客户端 RestHighLevelClient 使用(依赖+配置+客户端API测试源码)

    1.方式一 1.1 依赖 [不使用 spring-boot-starter-data-elasticsearch 就可以脱离 springboot 版本的限制,可以自行选择ES的版本]我用的是 spr ...

  7. SpringBoot中使用AMQ的两种方式二(Java配置、注解方式)

    使用@JmsListener注解方式 1. 工程目录 2. 引入依赖 <?xml version="1.0" encoding="UTF-8"?> ...

  8. Springboot集成Quartz定时任务yml文件配置方式

    构建maven项目,引入maven依赖包 其中quartz.quartz-jobs.spring-boot-starter-quartz.spring-context-support  四个依赖包为q ...

  9. SpringBoot集成Spring Data JPA多数据源(二)

    目录 1.引入依赖 2.application.yml配置文件 3.AtomikosJtaPlatform 4.事务管理器JPAAtomikosTransactionConfig 5.主数据源配置 6 ...

  10. SpringBoot 之 集成Caffeine本地缓存

    Caffeine的 github地址 使用版本说明: For Java 11 or above, use 3.0.x otherwise use 2.9.x SpringBoot 有两种使用 Caff ...

最新文章

  1. python windows ui自动化_appium+python+windows UI自动化 四.简单使用Appium客户端
  2. 怎样去判断一个网站是不是伪静态网站
  3. (转载)数据库范式及宽表窄表理解
  4. python基础系列教程——python所有包库的下载
  5. 如何利用反射来绕过泛型
  6. jmeter-00 JMeter 运行过程
  7. JavaFX真实世界应用程序:EIZO CuratOR Caliop
  8. Vue移动端项目——搜索联想建议功能的实现(结合watch属性和使用lodash防抖节流)
  9. docker artifactory-jcr
  10. CSDN搬家到博客园
  11. Meltdown(熔断漏洞)- Reading Kernel Memory from User Space/KASLR | 原文+中文翻译
  12. 明明很努力的写博客,为啥没人看?试试这些方法和工具(建议收藏)
  13. DBA必知的170张Oracle常用动态性能表介绍
  14. 【正点原子Linux连载】第三十五章 Linux内核顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
  15. multisim中轻触开关在哪_轻触开关在各种电子产品上的运用
  16. 第二届阿里巴巴大数据智能云上编程大赛亚军比赛攻略
  17. python中将科学计数法转数字
  18. iOS—网络实用技术OC篇网络爬虫-使用java语言抓取网络数据
  19. EMNLP 2021信息抽取论文合集
  20. 2022年Qt路线图

热门文章

  1. 容器技术Docker K8s 20 容器服务ACK基础与进阶-存储管理
  2. 阿里云云计算 45 阿里云云上安全
  3. 算法:从有序数组中移除重复的数据26. Remove Duplicates from Sorted Array
  4. 算法:First Missing Positive(求缺失的第一个正整数)
  5. 1949-2017闰年 c语言,C语言编写方案-万年历选编.doc
  6. 375.猜数字大小II
  7. crontab使用方法
  8. 对口升学计算机网络网络试题及答案,2011-2015计算机对口升学网络试题汇总
  9. 最新基于高德地图的android进阶开发(5)地图的基本操作、事件监听、用户UI、图层选择等
  10. linux centos目录结构(一)