经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
SpringBoot使用redis缓存List<Object>
来源:cnblogs  作者:ph7seven  时间:2018/10/25 9:36:40  对本文有异议

一、概述

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

  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序列化,

  1. package ;
  2.  
  3. import java.lang.reflect.Method;
  4.  
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cache.CacheManager;
  7. import org.springframework.cache.interceptor.KeyGenerator;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.data.redis.cache.RedisCacheManager;
  11. import org.springframework.data.redis.connection.RedisConnectionFactory;
  12. import org.springframework.data.redis.core.RedisTemplate;
  13. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  14. import org.springframework.data.redis.serializer.StringRedisSerializer;
  15. import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
  16.  
  17. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  18. import com.fasterxml.jackson.annotation.JsonInclude.Include;
  19. import com.fasterxml.jackson.annotation.PropertyAccessor;
  20. import com.fasterxml.jackson.databind.DeserializationFeature;
  21. import com.fasterxml.jackson.databind.ObjectMapper;
  22.  
  23. @Configuration
  24. public class RedisConfiguration {
  25. @Bean("jsonRedisCache")
  26. public CacheManager cacheManager(@Autowired RedisTemplate<String, Object> redisTemplate) {
  27. return new RedisCacheManager(redisTemplate);
  28. }
  29. @Bean
  30. public KeyGenerator keyGenerator() {
  31. return new KeyGenerator() {
  32. @Override
  33. public Object generate(Object target, Method method, Object... params) {
  34. StringBuilder sb = new StringBuilder();
  35. sb.append(target.getClass().getName());
  36. sb.append(method.getName());
  37. for (Object obj : params) {
  38. sb.append(obj.toString());
  39. }
  40. return sb.toString();
  41. }
  42. };
  43. }
  44.  
  45. @Bean
  46. public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory cf) {
  47. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
  48. redisTemplate.setKeySerializer(new StringRedisSerializer());
  49. redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer());
  50. redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
  51. redisTemplate.setConnectionFactory(cf);
  52. redisTemplate.afterPropertiesSet();
  53. return redisTemplate;
  54. }
  55.  
  56. @SuppressWarnings({ "unchecked", "rawtypes" })
  57. @Bean
  58. public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
  59. final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(
  60. Object.class);
  61. final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
  62. objectMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
  63. objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
  64. objectMapper.setSerializationInclusion(Include.NON_NULL);
  65. objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  66. objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  67. jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  68. return jackson2JsonRedisSerializer;
  69. }
  70. }  

 

三、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即可

  1. package ;
  2.  
  3. import java.util.HashMap;
  4. import java.util.List;
  5. import java.util.Map;
  6.  
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.cache.annotation.Cacheable;
  9. import org.springframework.stereotype.Service;
  10.  
  11. import com.es.entity.evralarm.EvrAlarm;
  12. import com.es.repository.evralarm.EvrAlarmDao;
  13.  
  14. @Service
  15. public class EvrAlarmCacheService {
  16.  
  17. @Autowired
  18. private EvrAlarmDao evrAlarmDao;
  19. @Cacheable(cacheNames="EvrAlarms",key="'EvrAlarm-'+#accountId")
  20. public List<EvrAlarm> getEvrAlarmByAccountId(String accountId){
  21. Map<String,Object> params = new HashMap<>();
  22. params.put("accountId", accountId);
  23. params.put("limit", 1);
  24. List<EvrAlarm> evrAlarms = evrAlarmDao.selectEvrAlarmByAccount(params);
  25. return evrAlarms;
  26. }
  27. }

  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])

  业务服务中原代码:

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

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

四、使用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:

  1. package com.xx.service.evralarm;
  2.  
  3. import java.util.HashMap;
  4. import java.util.List;
  5. import java.util.Map;
  6.  
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.data.redis.core.StringRedisTemplate;
  9. import org.springframework.data.redis.core.ValueOperations;
  10. import org.springframework.stereotype.Service;
  11.  
  12. import com.es.entity.evralarm.EvrAlarm;
  13. import com.es.repository.evralarm.EvrAlarmDao;
  14. import com.google.gson.Gson;
  15.  
  16. @Service
  17. public class EvrAlarmCacheService {
  18.  
  19. @Autowired
  20. private EvrAlarmDao evrAlarmDao;
  21. @Autowired
  22. private StringRedisTemplate redisTemplate;
  23. public List<EvrAlarm> getEvrAlarmByAccountId(String accountId){
  24. Map<String,Object> params = new HashMap<>();
  25. params.put("accountId", accountId);
  26. params.put("limit", 1);
  27. List<EvrAlarm> evrAlarms = evrAlarmDao.selectEvrAlarmByAccount(params);
  28. //redis缓存
  29. ValueOperations<String,String> vo = redisTemplate.opsForValue();
  30. Gson gson = new Gson();
  31. vo.set("EvrAlarm-"+accountId, gson.toJson(evrAlarms));
  32. return evrAlarms;
  33. }
  34. }

  

  业务服务从redis中取:

  从redis中获取key对应的value,得到string类型的value,使用Gson转成List<>对象

  1. 查询:
  1. /**
  2. * 根据账户ID查询最新告警信息
  3. * */
  4. public List<EvrAlarm> selectEvrAlarmByAccount(String accountId){
  5. //redis缓存中获取
  6. ValueOperations<String,String> vo = redisTemplate.opsForValue();
  7. String value = vo.get("EvrAlarm-"+accountId);
  8. Gson gson = new Gson();
  9. List<EvrAlarm> evrAlarms = gson.fromJson(value, List.class);
  10. return evrAlarms == null ? new ArrayList<>() : evrAlarms;
  11. }

 

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

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

  

 

最后问题解决

参考文档:

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

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号