SpringBoot使用redis缓存List

本文讲述了如何在SpringBoot中优化性能,利用Redis缓存List对象,解决了@Cacheable注解不起作用的问题,并探讨了使用StringRedisTemplate与RedisTemplate的场景,以及在处理跨服务缓存时遇到的类路径不一致问题。

一、概述

最近在做性能优化,之前有一个业务是这样实现的:

1.温度报警后第三方通讯管理机直接把报警信息保存到数据库

2.我们在数据库中添加触发器,(BEFORE INSERT)根据这条报警信息处理业务逻辑,在数据库中插入“其他业务数据”

3.前端setTimeout每隔5秒ajax去后端查询“其他业务数据”(查库)

优化后这样实现:

两个微服务,消息中间件专门一个服务,接收消息存入数据库,存入redis;业务服务直接从redis获取

1.MQTT订阅通讯管理机报警事件主题

2.发生报警后,java中根据报警信息保存“其他业务数据”到数据库并放入redis缓存

3.前端setTimeout每隔5秒ajax去后端查询“其他业务数据”(改为从redis中获取)

4.下一步计划使用WebSocekt,去掉前端setTimeout

二、SpringBoot配置redis

pom.xml、application.properties、@EnableCaching等等这些配置就不列出来了,大家可以百度,提一下RedisTemplate的配置

RedisTemplate<String, Object>可以直接存直接存List、Map等,使用jackson2JsonRedisSerializer序列化,

 

package ;
 
import java.lang.reflect.Method;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
 
@Configuration
public class RedisConfiguration {
 @Bean("jsonRedisCache")
 public CacheManager cacheManager(@Autowired RedisTemplate<String, Object> redisTemplate) {
 return new RedisCacheManager(redisTemplate);
 }
 
 @Bean
 public KeyGenerator keyGenerator() {
 return new KeyGenerator() {
 
 @Override
 public Object generate(Object target, Method method, Object... params) {
 StringBuilder sb = new StringBuilder();
 sb.append(target.getClass().getName());
 sb.append(method.getName());
 for (Object obj : params) {
 sb.append(obj.toString());
 }
 return sb.toString();
 }
 };
 }
 @Bean
 public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory cf) {
 RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
 redisTemplate.setKeySerializer(new StringRedisSerializer());
 redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer());
 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
 redisTemplate.setConnectionFactory(cf);
 redisTemplate.afterPropertiesSet();
 return redisTemplate;
 }
 
 @SuppressWarnings({ "unchecked", "rawtypes" })
 @Bean
 public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
 final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(
 Object.class);
 final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
 objectMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
 objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
 objectMapper.setSerializationInclusion(Include.NON_NULL);
 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
 jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
 return jackson2JsonRedisSerializer;
 }
} 

三、List对象存入redis遇到的问题

1.@Cacheable不起作用问题

刚开始,计划在service层方法上使用注解@Cacheable进行缓存,但是redis没有保存,最后百度得到答案:一个类中@Cacheable标注的方法不能被本类中其他方法调用,否则缓存不起作用

修改类方法调用后此问题解决

错误的方法调用:

SpringBoot使用redis缓存List

 

正确的调用:其他类调用该方法

这其中有个报错:No cache could be resolved for 'Builder[public java.util.List com.es.service.evralarm.EvrAlarmCacheService.getEvrAlarmByAccountId(java.lang.String)] caches=[] | key=''EvrAlarm-'+#accountId' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'' using resolver 'org.springframework.cache.interceptor.SimpleCacheResolver@7fbfc31a'. At least one cache should be provided per cache operation.

@Cacheable注解中添加cacheNames即可

 

package ;
 
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
 
import com.es.entity.evralarm.EvrAlarm;
import com.es.repository.evralarm.EvrAlarmDao;
 
@Service
public class EvrAlarmCacheService {
 
 @Autowired
 private EvrAlarmDao evrAlarmDao;
 
 @Cacheable(cacheNames="EvrAlarms",key="'EvrAlarm-'+#accountId")
 public List<EvrAlarm> getEvrAlarmByAccountId(String accountId){
 Map<String,Object> params = new HashMap<>();
 params.put("accountId", accountId);
 params.put("limit", 1);
 List<EvrAlarm> evrAlarms = evrAlarmDao.selectEvrAlarmByAccount(params);
 
 return evrAlarms;
 }
}

redis中存储的数据如下图:

SpringBoot使用redis缓存List

 

2.Could not resolve type id 'com.es.xx.evralarm.EvrAlarm' into a subtype of [simple type, class java.lang.Object]: no such class found

at [Source: [B@29a6e242; line: 1, column: 60] (through reference chain: java.util.ArrayList[0])

业务服务中原代码:

 

@Cacheable(cacheNames="EvrAlarms",key="'EvrAlarm-'+#accountId")
 public List<EvrAlarm> selectEvrAlarmByAccount(String accountId){
 Map<String,Object> params = new HashMap<>();
 params.put("accountId", accountId);
 return evrAlarmDao.selectEvrAlarmByAccount(params);
 }

看到一遍文档后明白了,根本原因是:两个微服务,实体类内容虽然一样,但是类路径不一样

四、使用StringRedisTemplate、RedisTemplate<String, Object>

进一步分析发现使用@Cacheable有问题,消息中间件收到第二条报警消息,如果业务系统没有处理第一条报警消息(redis中未删除,同样的key redis中已有一条)则redis中的信息不会更新

应该是:消息中间件每次接收消息,处理后都往redis中更新

使用RedisTemplate<String, Object>直接保存List对象,redis存储中会携带一个类路径信息("com.es.xx.evralarm.EvrAlarm"),业务服务获取的时候无法解析(两个实体类内容相同,类路径不同),只能使用StringRedisTemplate了,只能是在redis存取前后自己手动对象转json

使用Gson直接把要保存的List<>对象转成json再保存到redis

中间件所在服务存入redis:

 

package com.xx.service.evralarm;
 
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
 
import com.es.entity.evralarm.EvrAlarm;
import com.es.repository.evralarm.EvrAlarmDao;
import com.google.gson.Gson;
 
@Service
public class EvrAlarmCacheService {
 
 @Autowired
 private EvrAlarmDao evrAlarmDao;
 
 @Autowired
 private StringRedisTemplate redisTemplate;
 
 public List<EvrAlarm> getEvrAlarmByAccountId(String accountId){
 Map<String,Object> params = new HashMap<>();
 params.put("accountId", accountId);
 params.put("limit", 1);
 List<EvrAlarm> evrAlarms = evrAlarmDao.selectEvrAlarmByAccount(params);
 
 //redis缓存
 ValueOperations<String,String> vo = redisTemplate.opsForValue();
 Gson gson = new Gson();
 vo.set("EvrAlarm-"+accountId, gson.toJson(evrAlarms));
 return evrAlarms;
 }
}
业务服务从redis中取:
从redis中获取key对应的value,得到string类型的value,使用Gson转成List<>对象
1
<strong><span style="color: #ff0000"><span style="color: #000000">查询:</span></span></strong>

 

/**

* 根据账户ID查询最新告警信息

* */

 public List<EvrAlarm> selectEvrAlarmByAccount(String accountId){
 //redis缓存中获取
 ValueOperations<String,String> vo = redisTemplate.opsForValue();
 String value = vo.get("EvrAlarm-"+accountId);
 Gson gson = new Gson();
 List<EvrAlarm> evrAlarms = gson.fromJson(value, List.class);
 return evrAlarms == null ? new ArrayList<>() : evrAlarms;
 }<br><br>

业务操作删除、同时删除redis:

 

public void deleteAccountEvralarm(String accountId, String evrAlarmId){
 Map<String, Object> queryMap = new HashMap<>();
 queryMap.put("accountId", accountId);
 queryMap.put("evrAlarmId", evrAlarmId);
 accountEvralarmDao.deleteByPrimaryKey(queryMap);
 //redis删除缓存
 redisTemplate.delete("EvrAlarm-"+accountId);
 }

欢迎工作一到八年的Java工程师朋友们加入Java高级交流群:854630135

本群提供免费的学习指导 架构资料 以及免费的解答

不懂得问题都可以在本群提出来 之后还会有直播平台和讲师直接交流噢

哦对了,喜欢就别忘了关注一下哦~

 

Spring Boot应用中使用Redis缓存用户行为日志,可以有效提升日志处理的性能,减少数据库压力,并实现快速读写操作。以下是具体的实现方法: ### 1. 添加依赖 首先,在项目的 `pom.xml` 文件中添加Spring Data Redis和Lettuce连接池的依赖,用于支持Redis的集成与操作: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </dependency> ``` Lettuce 是一个高性能的 Redis 客户端,适合处理高并发场景下的 Redis 连接需求[^2]。 ### 2. 配置 Redis 连接 在 `application.properties` 或 `application.yml` 文件中配置 Redis 的连接信息,包括主机地址、端口、数据库索引等: ```properties spring.redis.host=localhost spring.redis.port=6379 spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.max-idle=8 spring.redis.timeout=6000ms ``` 这些配置确保 Spring Boot 能够正确连接到 Redis 服务器并管理连接池[^2]。 ### 3. 定义用户行为日志实体类 创建一个 `UserActivityLog` 实体类,用于表示用户行为日志的数据结构: ```java public class UserActivityLog { private String userId; private String action; private LocalDateTime timestamp; // Constructors, Getters and Setters } ``` ### 4. 使用 RedisTemplate 操作缓存 通过 `RedisTemplate` 可以进行 Redis 的基本操作。以下是一个服务类示例,展示了如何将用户行为日志储到 Redis 中: ```java @Service public class UserActivityService { private final RedisTemplate<String, UserActivityLog> redisTemplate; public UserActivityService(RedisTemplate<String, UserActivityLog> redisTemplate) { this.redisTemplate = redisTemplate; } public void logUserActivity(String userId, String action) { String key = "user:activity:" + userId; UserActivityLog log = new UserActivityLog(); log.setUserId(userId); log.setAction(action); log.setTimestamp(LocalDateTime.now()); // 使用 opsForList 将日志以列表形式Redis redisTemplate.opsForList().leftPush(key, log); } public List<UserActivityLog> getUserActivityLogs(String userId) { String key = "user:activity:" + userId; return redisTemplate.opsForList().range(key, 0, -1); } } ``` 此代码通过 `RedisTemplate` 的 `opsForList()` 方法将用户行为日志作为列表储到 Redis 中,便于后续的查询和分析[^4]。 ### 5. 控制器层调用服务 在控制器中注入 `UserActivityService` 并调用其方法记录用户行为日志: ```java @RestController @RequestMapping("/api/activity") public class UserActivityController { private final UserActivityService userActivityService; public UserActivityController(UserActivityService userActivityService) { this.userActivityService = userActivityService; } @PostMapping("/log") public ResponseEntity<String> logActivity(@RequestParam String userId, @RequestParam String action) { userActivityService.logUserActivity(userId, action); return ResponseEntity.ok("Activity logged successfully"); } @GetMapping("/logs") public ResponseEntity<List<UserActivityLog>> getActivityLogs(@RequestParam String userId) { List<UserActivityLog> logs = userActivityService.getUserActivityLogs(userId); return ResponseEntity.ok(logs); } } ``` 此控制器提供了两个接口:一个用于记录用户行为日志,另一个用于查询特定用户的日志记录。 ### 6. 数据持久化与清理策略 虽然 Redis 是内数据库,但为了防止数据丢失,可以配置 Redis 的持久化策略(如 RDB 快照或 AOF 日志)将数据定期保到磁盘。此外,还可以设置过期时间,清理过期的用户行为日志: ```java redisTemplate.expire("user:activity:" + userId, 7, TimeUnit.DAYS); ``` 此代码设置用户行为日志的过期时间为 7 天,避免数据无限增长[^1]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值