博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot使用redis缓存List<Object>
阅读量:6049 次
发布时间:2019-06-20

本文共 10080 字,大约阅读时间需要 33 分钟。

一、概述

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

  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;@Configurationpublic class RedisConfiguration {	@Bean("jsonRedisCache")	public CacheManager cacheManager(@Autowired RedisTemplate
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
redisTemplate(@Autowired RedisConnectionFactory cf) { RedisTemplate
redisTemplate = new RedisTemplate
(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(cf); redisTemplate.afterPropertiesSet(); return redisTemplate; } @SuppressWarnings({ "unchecked", "rawtypes" }) @Bean public Jackson2JsonRedisSerializer
jackson2JsonRedisSerializer() { final Jackson2JsonRedisSerializer 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;@Servicepublic class EvrAlarmCacheService {	@Autowired	private EvrAlarmDao evrAlarmDao;		@Cacheable(cacheNames="EvrAlarms",key="'EvrAlarm-'+#accountId")	public List
getEvrAlarmByAccountId(String accountId){ Map
params = new HashMap<>(); params.put("accountId", accountId); params.put("limit", 1); List
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
selectEvrAlarmByAccount(String accountId){ Map
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;@Servicepublic class EvrAlarmCacheService {	@Autowired	private EvrAlarmDao evrAlarmDao;		@Autowired	private RedisTemplate
redisTemplate; public List
getEvrAlarmByAccountId(String accountId){ Map
params = new HashMap<>(); params.put("accountId", accountId); params.put("limit", 1); List
evrAlarms = evrAlarmDao.selectEvrAlarmByAccount(params); //redis缓存 ListOperations
lo = redisTemplate.opsForList(); lo.rightPush("EvrAlarm-"+accountId, evrAlarms); return evrAlarms; }}

  

存储后redis中的数据结构入下图:

 

  业务服务获取redis中的数据:

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

  报错了:

  报错信息很明了,“no such class found”,还是上面的问题,两个服务都有实体类“EvrAlarm”,内容相同,类路径不同

  把消息服务中“EvrAlarm”实体类类路径修改后,redis存储结构如下:

  

  业务服务获取redis中的数据:

/**		 * 根据账户ID查询最新告警信息		 * */		public List
selectEvrAlarmByAccount(String accountId){ //redis缓存中获取 ListOperations
lo = redisTemplate.opsForList(); List
evrAlarms = (List
) 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;@Servicepublic class EvrAlarmCacheService {	@Autowired	private EvrAlarmDao evrAlarmDao;		@Autowired	private StringRedisTemplate redisTemplate;		public List
getEvrAlarmByAccountId(String accountId){ Map
params = new HashMap<>(); params.put("accountId", accountId); params.put("limit", 1); List
evrAlarms = evrAlarmDao.selectEvrAlarmByAccount(params); //redis缓存 ValueOperations
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
selectEvrAlarmByAccount(String accountId){ //redis缓存中获取 ValueOperations
vo = redisTemplate.opsForValue(); String value = vo.get("EvrAlarm-"+accountId); Gson gson = new Gson(); List
evrAlarms = gson.fromJson(value, List.class); return evrAlarms == null ? new ArrayList<>() : evrAlarms; }

 

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

public void deleteAccountEvralarm(String accountId, String evrAlarmId){    	Map
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

 

转载于:https://www.cnblogs.com/ph7seven/p/9845234.html

你可能感兴趣的文章
存储过程中拼接的变量和点的问题
查看>>
ASP.NET那点不为人知的事(一)
查看>>
3.16
查看>>
表单文件上传与文件下载
查看>>
下午考
查看>>
创建字符设备的三种方法
查看>>
走在网页游戏开发的路上(六)
查看>>
nginx 配置的server_name参数(转)
查看>>
Uva592 Island of Logic
查看>>
C++基础代码--20余种数据结构和算法的实现
查看>>
footer固定在页面底部的实现方法总结
查看>>
nginx上传文件大小
查看>>
HDU 2243 考研路茫茫——单词情结(自动机)
查看>>
Dubbo OPS工具——dubbo-admin & dubbo-monitor
查看>>
Dungeon Master ZOJ 1940【优先队列+广搜】
查看>>
Delphi 中的 XMLDocument 类详解(5) - 获取元素内容
查看>>
2013年7月12日“修复 Migration 测试发现的 Bug”
查看>>
学习vue中遇到的报错,特此记录下来
查看>>
CentOS7 编译安装 Mariadb
查看>>
jstl格式化时间
查看>>