SpringBoot使用redis缓存List<Object>
一、概述
最近在做性能优化,之前有一个业务是这样实现的:
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标注的方法不能被本类中其他方法调用,否则缓存不起作用
修改类方法调用后此问题解决
错误的方法调用:

正确的调用:其他类调用该方法
这其中有个报错: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中存储的数据如下图:

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中更新
1.使用RedisTemplate<String, Object>
使用RedisTemplate<String, Object>存储
package ; 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.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
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; @Autowired
private RedisTemplate<String, Object> 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缓存
ListOperations<String, Object> lo = redisTemplate.opsForList();
lo.rightPush("EvrAlarm-"+accountId, evrAlarms);
return evrAlarms;
}
}
存储后redis中的数据结构入下图:

业务服务获取redis中的数据:
/**
* 根据账户ID查询最新告警信息
* */
public List<EvrAlarm> selectEvrAlarmByAccount(String accountId){
//redis缓存中获取
ListOperations<String,Object> lo = redisTemplate.opsForList();
Object obj = lo.index("EvrAlarm-"+accountId, 0);
List<EvrAlarm> evrAlarms = (List<EvrAlarm>) obj;
return evrAlarms == null ? new ArrayList<>() : evrAlarms;
}
报错了:

报错信息很明了,“no such class found”,还是上面的问题,两个服务都有实体类“EvrAlarm”,内容相同,类路径不同
把消息服务中“EvrAlarm”实体类类路径修改后,redis存储结构如下:
业务服务获取redis中的数据:
/**
* 根据账户ID查询最新告警信息
* */
public List<EvrAlarm> selectEvrAlarmByAccount(String accountId){
//redis缓存中获取
ListOperations<String,Object> lo = redisTemplate.opsForList();
List<EvrAlarm> evrAlarms = (List<EvrAlarm>) lo.index("EvrAlarm-"+accountId, 0);
return evrAlarms == null ? new ArrayList<>() : evrAlarms;
}
没有报错能够正常获取:

2.使用StringRedisTemplate
StringRedisTemplate不需要考虑存储对象的类路径问题,存储前直接把对象转成json字符串,获取的时候再把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<>对象
查询:
/**
* 根据账户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;
}
业务操作删除、同时删除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);
}
最后问题解决
参考文档:
http://www.mamicode.com/info-detail-2267905.html
https://blog.csdn.net/ranweizheng/article/details/42267803
https://yq.aliyun.com/ziliao/444278
https://blog.csdn.net/hanchao5272/article/details/79051364
SpringBoot使用redis缓存List<Object>的更多相关文章
- springboot整合redis缓存
使用springBoot添加redis缓存需要在POM文件里引入 org.springframework.bootspring-boot-starter-cacheorg.springframewor ...
- SpringBoot 整合 Redis缓存
在我们的日常项目开发过程中缓存是无处不在的,因为它可以极大的提高系统的访问速度,关于缓存的框架也种类繁多,今天主要介绍的是使用现在非常流行的NoSQL数据库(Redis)来实现我们的缓存需求. Spr ...
- springboot集成redis缓存
1.pom.xml增加redis缓存起步依赖(spring-boot-starter-parent包含许多starter版本) <dependency> <groupId>or ...
- SpringBoot整合redis缓存(一)
准备工作 1.Linux系统 2.安装redis(也可以安装docker,然后再docker中装redis,本文章就直接用Linux安装redis做演示) redis下载地址: 修改redis,开启远 ...
- Java SpringBoot使用Redis缓存和Ehcache
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http:// ...
- springboot整合redis缓存一些知识点
前言 最近在做智能家居平台,考虑到家居的控制需要快速的响应于是打算使用redis缓存.一方面减少数据库压力另一方面又能提高响应速度.项目中使用的技术栈基本上都是大家熟悉的springboot全家桶,在 ...
- springboot 用redis缓存整合spring cache注解,使用Json序列化和反序列化。
springboot下用cache注解整合redis并使用json序列化反序列化. cache注解整合redis 最近发现spring的注解用起来真的是很方便.随即产生了能不能吧spring注解使用r ...
- SpringBoot(七) - Redis 缓存
1.五大基本数据类型和操作 1.1 字符串-string 命令 说明 set key value 如果key还没有,那就可以添加,如果key已经存在了,那会覆盖原有key的值 get key 如果ke ...
- SpringBoot使用redis缓存List
一.概述 最近在做性能优化,之前有一个业务是这样实现的: 1.温度报警后第三方通讯管理机直接把报警信息保存到数据库: 2.我们在数据库中添加触发器,(BEFORE INSERT)根据这条报警信息处理业 ...
随机推荐
- 【转】七牛免费SSL证书,配置自定义域名CDN加速
原文链接:https://excaliburhan.com/post/use-qiniu-ssl-and-cdn.html 申请七牛SSL证书 其实,七牛在很早之前就支持CDN使用https,但是他要 ...
- JMX configuration for Tomcat
Window下执行步骤: D:\apache-tomcat-7.0.57\bin\catalina.bat set CATALINA_OPTS=-Dcom.sun.management.jmxremo ...
- SQLserver 一种简单的GUI方式创建DBlink copy 表数据的方法
1. 在sqlserver 上面使用GUI的方式创建dblink 首先打开查询分析器 在如下的位置处右键 -新建连接服务器 输入需要copy数据的服务器 输入ip地址 然后建立连接 在打开查询分析器进 ...
- Angular service定义服务
<!DOCTYPE html><html ng-app="myApp"><head lang="en"> <meta ...
- 我终于激活Windows Server2008 R2了!!
经过我不懈的努力,在重装两次系统后,我终于实现了win2008的KMS激活.这个方法可以避免虚拟机架设KMS服务器的麻烦.现将激活方法发布如下. 首先要选择安装的操作系统.Windows Server ...
- Delphi.XE2破解方法
我安装的是Delphi.XE2.RTM.v16.0.4256.43595.Lite.v5.0 ,安装完后打开显示是15天试用. 退出XE2后把C:\Program Files\Embarcadero\ ...
- 【转】fscanf 跳过空格,读取一行
fscanf(fp, "%s", sLineWord); 以上语句,在读取一行数据时,如何遇到该行数据有空格,那么读到空格处就停止,不再继续向下读. 若想遇到空格继续读取,读取完整 ...
- 学习笔记12之通过ajax动态添加选项
- 美图美妆由Try Try接手运营
美图又把一个拖累营收的业务转让出去了. 美图的电商业务——美图美妆应用在向用户发布终止运营的公告后,宣布把业务交给了寺库旗下公司 Try Try 运营.Try Try 接手了美图美妆的所有管理运营权, ...
- jenkins--使用命令行自动启动Jenkins的job
Jenkins作为持续集成强大的开源工具,除了使用界面它还有强大的cli命令. 1 自动启动jenkins 的job: 启动不带参数的job: curl --user USER:PASSWORD JE ...