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遇到的問題
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中更新
使用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<>物件
查詢:
/** * 根據賬戶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