美文网首页Java高级进阶
SpringBoot使用redis缓存List

SpringBoot使用redis缓存List

作者: e4e9aa34f536 | 来源:发表于2018-10-25 15:26 被阅读1次

    一、概述

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

    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 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;

    @Service

    public 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中更新

    使用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 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<>对象

    1

    查询:

    /**

    * 根据账户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);

    }

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

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

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

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

    相关文章

      网友评论

        本文标题:SpringBoot使用redis缓存List

        本文链接:https://www.haomeiwen.com/subject/afcctqtx.html