springboot cache redis优雅实现缓存
mhr18 2024-11-19 06:58 19 浏览 0 评论
导读
你还在代码里面写查询数据,然后写入缓存代码吗?没错,你曾经应该都是这么做,做得很累,代码很臃肿,那么从本文开始,可以抛掉了,让我们缓存代码更加优雅起来,更加简单起来。
Spring Cache前身
Spring cache是使用org.springframework.cache.Cache和org.springframework.cache.CacheManager来管理接口,其中CacheManager是Spring提供的各种缓存技术的抽象接口。而Cache接口包含缓存的各种操作。
Cache接口是缓存组件规范,操作各种缓存数据,提供支持各种缓存框架实现,如:RedisCache,ConcurrentMapCache,EhcacheCache等,RedisCache是我们最常用的。
Spring cache 使用注解来简化实现缓存开发,是基于AOP技术实现。
Spring CacheManger相关技术说明
SimpleCacheManager 使用简单的来存储缓存,单一存储,很少用
ConcurrentMapCacheManager 使用ConcurrentMap作为缓存技术(默认),需要显式的删除缓存,无过期机制
NoOpCacheManager 不用存储缓存
EhCacheCacheManager 使用EhCache作为缓存技术,以前在很老系统可能用到
GuavaCacheManager 使用google guava cache作为缓存技术
CaffeineCacheManager 是使用Java8对Guava缓存的重写,spring5(springboot2)开始用Caffeine取代guava
HazelcastCacheManager 使用Hazelcast作为缓存技术
JCacheCacheManager 使用JCache标准的实现作为缓存技术,如Apache Commons JCS
RedisCacheManager 使用Redis作为缓存技术
最常用:RedisCacheManager,系统默认使用ConcurrentMapCacheManager是单机JVM内存,不适合分布式多台服务
缓存概念以及注解说明
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存组件,如上说明
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其进行缓存(必用)
@CacheEvict 清空缓存(必用)
@CachePut 保证方法被调用,更新结果被缓存。常用于更新后操作(可用)
@EnableCaching 开启基于注解的缓存(必须)
@CacheConfig 统一配置本类的缓存注解的属性(可用)
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略
@Cacheable/@CachePut/@CacheEvict 重要参数说明
参数:value 缓存名称,必填,如:@Cacheable(value = "user")
参数:key 缓存key,非必填,使用SpEL表达式,一般用在参数中,如:@Cacheable(value = "user",key = "#userId")
参数:condition 表达式条件满足缓存,非必填,使用SpEL表达式,满足条件true缓存,false不缓存,如:@Cacheable(value = "user:condition",condition="#userPO.userName.length()>2")
参数:unless 表达式条件满足不缓存,非必填,使用SpEL表达式,跟condition参数反向,如:@Cacheable(value = "user:unless",unless ="#userPO.id>3")
参数:sync @Cacheable特有,多线程情况下可能出现重复载入缓存情况,设置为true可解决,默认是false
参数:allEntries @CacheEvict特有,清除下面value下面所有的::缓存数据,设置不设置key参数都不影响删除结果,如:@CacheEvict(value = "user",key = "#userPO.id",allEntries = true)
参数:beforeInvocation
@CacheEvict特有,是否执行方法前就清除了数据,默认是false执行方法后再清除缓存,设置true执行方法前就清除了数据。
如:@CacheEvict(value = "user",key = "#userPO.id",allEntries = true,beforeInvocation = true)
注解:@CachePut 更新缓存后,又重新把数据放入缓存,如:@CachePut(value = "user",key = "#userPO.id")
SpEL表达式说明
名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数
如:getUser(User user),可以通过#user.id获得参数 #user.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效) #result
SpEL运算符
类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&,||,!,and,or,not,between,instanceof
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],^[…],$[…]
开始项目
新建一个springboot项目,导入springboot依赖包版本,同时项目新增改查依赖mybatis-plus具体整合,日志用lombok,不懂整合这两个技术的可以咨询我
<!-- 引入springboot版本依赖包 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
<!-- 引入springboot-web版本依赖包 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
引入SpringBoot Cache依赖包
<!-- springboot cache 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
整合Redis依赖包
<!-- 整合 Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置application.properties
server.port=8010
server.servlet.context-path=/
################ Redis 基础配置 ##############
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 链接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000
编写SpringBoot启动类
@SpringBootApplication
public class UserProvideApp
{
public static void main( String[] args )
{
SpringApplication.run(UserProvideApp.class, args);
}
}
编写开启@EnableCaching注解类
创建SpringCacheConfig类,在该类上新增@EnableCaching注解,该注解是必须要配置的,也可以直接配置在启动类UserProvideApp上
/**
* 开启 @EnableCaching 注解,必须要开启才能用到缓存注解类<br>
* 这个注解可以配置在启动类上
*/
@Configuration
@EnableCaching
public class SpringCacheConfig {
}
新建UserPO
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("user")
public class UserPO implements Serializable {
private static final long serialVersionUID=1L;
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String pwd;
/**
* 回答总数
*/
private Integer qaReplyCount;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
新建UserCacheService缓存类
UserCacheServiceImpl实现类如下,UserCacheService自行实现
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.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hsb.user.po.UserPO;
import com.hsb.user.provide.dao.UserDAO;
import lombok.extern.slf4j.Slf4j;
/**
* 用户缓存,业务接口
*/
@Service
@Slf4j
public class UserCacheServiceImpl implements UserCacheService{
@Autowired
UserDAO userDAO;
/**
* 参数缓存<br>
* value 参数必填,缓存名称<br>
* key 缓存key,参数表达式要和参数名称一样<br>
* 最终缓存key user:(userId)组合,如:user::2<br>
*/
@Cacheable(value = "user",key = "#userId")//user:1
public UserPO getByUserId(Long userId) {
log.info("查询用户ID={}",userId);
return userDAO.selectById(userId);
}
/**
* 对象参数缓存<br>
* value 参数必填,缓存名称<br>
* key 缓存key,参数表达式要和对象参数名称一样<br>
* 最终缓存key user:po:(userId)组合<br>
*/
@Cacheable(value = "user:po",key = "#userPO.id")//user:1
public UserPO getByUser(UserPO userPO) {
log.info("查询用户userPO={}",userPO);
QueryWrapper<UserPO> queryWrapper=new QueryWrapper<UserPO>(userPO);
return userDAO.selectOne(queryWrapper);
}
/**
* 表达式缓存,满足条件缓存<br>
* value 参数必填,缓存名称<br>
* condition 表达式,满是缓存<br>
* 最终缓存key user:condition:(userName)组合<br>
*/
@Cacheable(value = "user:condition",key = "#userPO.userName",condition="#userPO.userName.length()>2")
public UserPO getByUserCondition(UserPO userPO) {
log.info("查询用户UserCondition={}",userPO);
QueryWrapper<UserPO> queryWrapper=new QueryWrapper<UserPO>(userPO);
return userDAO.selectOne(queryWrapper);
}
/**
* 表达式缓存,满足条件不缓存,反之缓存,跟condition参数刚刚相反<br>
*
* value 参数必填,缓存名称<br>
* unless 表达式,满足不缓存<br>
* 最终缓存key user:cunless:组合<br>
*/
@Cacheable(value = "user:unless",unless ="#userPO.id>3")
public UserPO getByUserUnless(UserPO userPO) {
log.info("查询用户UserUnless={}",userPO);
QueryWrapper<UserPO> queryWrapper=new QueryWrapper<UserPO>(userPO);
return userDAO.selectOne(queryWrapper);
}
/**
* 1、删除数据<br>
*
* 2、清除缓存<br>
*
* 3、参数:allEntries 清除下面value下面所有的::缓存数据,设置不设置key参数不影响删除结果<br>
* 如:清除:user::xx 所有数据<br>
* 4、如果想删除指定value+key,去掉allEntries参数即可<br>
*
* @param userPO
* @return
*/
@CacheEvict(value = "user",key = "#userPO.id",allEntries = true)
public int delUserPo(UserPO userPO) {
log.info("删除用户userPO={}",userPO);
QueryWrapper<UserPO> queryWrapper=new QueryWrapper<UserPO>(userPO);
return userDAO.delete(queryWrapper);
}
/**
* 1、删除数据<br>
* 2、清除指定key缓存,不是删除value下面所有所有<br>
* 3、beforeInvocation 是否执行方法前就清除了数据,默认是false执行方法后再清除缓存,设置true执行方法前就清除了数据<br>
* 4、异常测试,指定方法前指定了删除操作<br>
*
* @param userPO
* @return
*/
@CacheEvict(value = "user",key = "#userPO.id",beforeInvocation = true)
public int delUserPoBybeforeInvocation(UserPO userPO) {
log.info("删除用户beforeInvocation-userPO={}",userPO);
int i=1/0;//异常测试,指定方法前指定了删除操作
QueryWrapper<UserPO> queryWrapper=new QueryWrapper<UserPO>(userPO);
return userDAO.delete(queryWrapper);
}
/**
* @CachePut 注解使用<br>
*
* 更新用户后,从新把更新信息放入缓存<br>
*
* @param userPO
* @return
*/
@CachePut(value = "user",key = "#userPO.id")
public UserPO updateUser(UserPO userPO) {
log.info("更新用户userPO={}",userPO);
int rt=userDAO.updateById(userPO);
if(rt<=0) {
throw new RuntimeException("更新用户失败userPo="+userPO);
}
return getByUser(userPO);
}
}
编写UserCacheController
/**
* 用户缓存测试
*/
@RestController
@RequestMapping("/user/cache")
public class UserCacheController {
@Autowired
UserCacheService userCacheService;
@GetMapping("/getByUserId")
public UserPO getByUserId(Long userId) {
return userCacheService.getByUserId(userId);
}
/**
* 对象参数缓存
* value 参数必填,缓存名称
* key 缓存key,参数表达式要和对象参数名称一样
* 最终缓存key user:po:(userId)组合
*/
@GetMapping("/getByUser")
public UserPO getByUser(UserPO userPO) {
return userCacheService.getByUser(userPO);
}
/**
* 表达式缓存,满足条件缓存
* value 参数必填,缓存名称
* condition 表达式,满是缓存
* 最终缓存key user:condition:(userName)组合
*/
@GetMapping("/getByUserCondition")
public UserPO getByUserCondition(UserPO userPO){
return userCacheService.getByUserCondition(userPO);
}
/**
* 表达式缓存,满足条件不缓存,跟condition参数刚刚相反
*
* value 参数必填,缓存名称
* unless 表达式,满足不缓存
* 最终缓存key user:cunless:组合
*/
@GetMapping("/getByUserUnless")
public UserPO getByUserUnless(UserPO userPO) {
return userCacheService.getByUserUnless(userPO);
}
/**
* 删除数据
*
* 清除缓存
*
* 参数:allEntries 清除下面所有的缓存数据
*
* @param userPO
* @return
*/
@GetMapping("/delUserPo")
public int delUserPo(UserPO userPO){
return userCacheService.delUserPo(userPO);
}
/**
* 删除数据
*
* beforeInvocation 是否执行方法前就清除了数据,默认是false执行方法后再清除缓存,设置true执行方法前就清除了数据
*
* @param userPO
* @return
*/
@GetMapping("/delUserPoBybeforeInvocation")
public int delUserPoBybeforeInvocation(UserPO userPO){
return userCacheService.delUserPoBybeforeInvocation(userPO);
}
/**
* @CachePut 注解使用
*
* 更新用户后,从新把更新信息放入缓存
*
* @param userPO
* @return
*/
@GetMapping("/updateUser")
public UserPO updateUser(UserPO userPO){
return userCacheService.updateUser(userPO);
}
}
启动服务
启动服务,就可以通过浏览器访问测试每个使用的效果了。
测试地址 如:http://127.0.0.1:8010/user/cache/getByUserId?userId=8
看到缓存结果
其他测试接口,如有不懂的,可以关注我,跟我私聊。
从上面可以看到缓存结果是一推密密麻麻的乱码,看不懂,这是因为默认采用的是JdkSerializationRedisSerializer序列化,如果你想把这个结果看得懂,如结果使用json格式,那么也是转换成指定的序列即。
引入Fastjson依赖
<!--alibaba fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
新增序列化Bean
在SpringCacheConfig类上,编写配置Fastjson序列化对象,如下
/**
* 配置Fastjson序列化对象种作为缓存结果
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
//使用fastjson序列化对象
RedisSerializer<?> valSeri=new GenericFastJsonRedisSerializer();
return RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valSeri));
}
重启服务
重新访问测试上面路径,就可以看到缓存结果是json格式了,如下图所示:
缓存过期时间
经过以上发现,没有哪个参数可以设置一下过期时间,现在需求就是设置一下缓存过期时间,那么如何做呢?我们需要重新编辑SpringCacheConfig类上面序列化Bean代码,如下重写代码
/**
* 配置Fastjson序列化对象种作为缓存结果
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
//使用fastjson序列化对象
RedisSerializer<?> valSeri=new GenericFastJsonRedisSerializer();
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(valSeri)
).entryTtl(Duration.ofSeconds(30));//所有的缓存都是默认30秒后过期
return redisCacheConfiguration;
}
这样就可以设置所有缓存key都是30秒以上都过期了,很简单吧。
现在问题又来了,如果需要对特定的缓存名称,设置不同时间呢?那又如何处理?我们得继续改进配置SpringCacheConfig缓存序列相关配置对象,代码如下
//================配置特定key缓存过期时间================
/**
* 新增配置缓存管理Bean
* @param redisConnectionFactory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
CacheManager cacheManager=new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)
, defaulRedisCacheConfiguration() //默认缓存配置30秒过期
, redisCacheConfigurationMap());//指定缓存key过期
return cacheManager;
}
/**
* 配置过期时间
* @param seconds
* @return
*/
private RedisCacheConfiguration redisCacheConfiguration(Long seconds) {
//使用fastjson序列化对象
RedisSerializer<?> valSeri=new GenericFastJsonRedisSerializer();
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(valSeri)
).entryTtl(Duration.ofSeconds(seconds));//所有的缓存都是默认30秒后过期
return redisCacheConfiguration;
}
/**
* 配置多个value配置使用不同配置
* @return
*/
private Map<String, RedisCacheConfiguration> redisCacheConfigurationMap(){
Map<String, RedisCacheConfiguration> map=new HashMap<String, RedisCacheConfiguration>();
map.put("user", redisCacheConfiguration(20l));//指定value=user 缓存过期为20秒
map.put("user:po", redisCacheConfiguration(15l));//指定value=user:po 缓存过期为15秒
return map;
}
代码里面就指定了特定value缓存名称对应的过期时间,如果有新的需要单独处理,新增代码Map对象即可,重启服务,测试通过
总结
利用springboot整合spring cache,redis,fastjson优雅实现了缓存存在,大大地提高开发效率,满足了部分缓存业务实现功能,唯一不足地方就是该框架缓存过期时间设计的不是非常灵活,有点繁琐,但是整体还是不错的,如大家使用或者觉得不理解的地方,欢迎大家讨论。
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)