SpringBoot使用redis缓存List
一、概述
最近在做性能优化,之前有一个业务是这样实现的:
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遇到的问题
[email protected]不起作用问题
刚开始,计划在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 '[email protected]1a'. 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: [[email protected]; 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
本群提供免费的学习指导 架构资料 以及免费的解答
不懂得问题都可以在本群提出来 之后还会有直播平台和讲师直接交流噢
哦对了,喜欢就别忘了关注一下哦~