最近這幾天一直在加班心情很是郁悶桨武,項目中遇到的問題很多,加上很多遺留代碼镐作,最主要的是對業(yè)務也不熟悉藏姐,工作經常事倍功半。今天寫這個也是因為工作中遇到了和Redis相關的問題该贾,自己單獨寫了一個demo羔杨,也算是一個工作總結吧。spring boot中使用Redis地方可以作為緩存杨蛋、可以存儲session兜材,今天主要說下緩存方面相關的知識,內容主要分為兩部分:一是直接操作Redis逞力,即顯性操作Redis曙寡;二是隱式調用,主要是使用spring cache結合Redis寇荧。
一举庶、顯性操作Redis
其實之前自己做spring boot項目操作Redis使用的是自己封裝的工具類,但是本質上使用的還是redisTemplate揩抡,但是這次項目中發(fā)現(xiàn)有使用redisTemplate也有stringRedisTemplate户侥。說一下自己遇到的問題镀琉,自己在service注入redisTemplate,并向Redis存儲數(shù)據報錯添祸,異常信息是類型轉換錯誤滚粟,List不能轉成string。后來才知道項目專門有Redis的模塊刃泌,而默認使用的序列化策略是StringRedisSerializer凡壤,自己只有使用Gson將List轉成json串存放,然后取出的時候再反序列化轉成List耙替,當然也可以自己再重新配置一個RedisTemplate亚侠。RedisTemplate默認采取的序列化策略是jdk的序列化方式,但是數(shù)據存放到Redis后數(shù)據不直觀俗扇,因此一般會使用json的序列化策略硝烂。下面我們通過redisTemplate代碼看下:
public RedisTemplate() {
}
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
defaultUsed = true;
}
}
if (this.enableDefaultSerializer && defaultUsed) {
Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
}
if (this.scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}
this.initialized = true;
}
afterPropertiesSet方法會在RedisTemplate設置屬性值之后執(zhí)行,然后會設置序列化方式铜幽,默認的話是使用JdkSerializationRedisSerializer滞谢。但是我們一般會使用自定義的序列化策略。
下面通過demo來測試一下自定義的RedisTemplate除抛,我們Java代碼的方式定義配置狮杨,代碼如下:
@Configuration
public class RedisConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisConfig.class);
/**
* 自定義redisTemplate序列化策略,注入redisTemplate時會使用該方法返回的bean
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
LOGGER.info(">>>> custom redisTemplate configuration start <<<<");
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 配置jackson序列化策略默認
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
// 配置其他的序列化策略
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
// 另外一種json序列化策略
//redisTemplate.setHashValueSerializer(RedisSerializer.json());
return redisTemplate;
}
}
上面是自定義類key到忽、hashKey橄教、hashValue、RedisSerializer以及默認序列化策略喘漏,默認序列化策略使用的Jackson2JsonRedisSerializer护蝶。此外還可以使用RedisSerializer.json()策略,它實際上是GenericJackson2JsonRedisSerializer序列化策略翩迈,關于這兩種json序列化策略的區(qū)別持灰,建議百度一下,這里不細述了负饲。而RedisSerializer.string()實際上是StringRedisSerializer.UTF_8堤魁。配置好RedisTemplate以后通過一個簡單功能測試看一下效果。
定義一個controller绽族,定義一個條件查詢的接口姨涡,代碼如下:
@RestController
@RequestMapping("/redis")
public class RedisController {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisController.class);
private Gson gson = new Gson();
@Autowired
private RedisService redisService;
@Autowired
private StringRedisService stringRedisService;
@Autowired
private DataConverter converter;
/**
* 根據條件查詢列表
* @param bookDTO
* @return
*/
@PostMapping("/getList")
public List<Book> getAllList(@RequestBody BookDTO bookDTO) {
LOGGER.info(">>>> request parameters are :{} <<<<",gson.toJson(bookDTO));
Book book = converter.convert(bookDTO);
return redisService.getAllList(book);
}
}
調用service查詢的時候先查詢Redis,Redis沒有再查詢數(shù)據庫吧慢,service代碼如下:
@Service
public class RedisServiceImpl implements RedisService {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisServiceImpl.class);
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private BookMapper bookMapper;
/**
* 存放book list的key
*/
private static final String BOOK_LIST_KEY = "book_list_key";
/**
* 存放每位作者書的hash key的前綴涛漂,即hash key的值為前綴 + author
*/
private static final String BOOK_AUTHOR_KEY_PREFIX = "book_list_key_prefix_";
@Override
public List<Book> getAllList(Book book) {
// redisTemplate
List<Book> bookList = (List<Book>) redisTemplate.opsForHash().get(BOOK_LIST_KEY,BOOK_AUTHOR_KEY_PREFIX + book.getAuthor());
if (CollectionUtils.isNotEmpty(bookList)) {
return bookList;
}
bookList = bookMapper.queryByCondition(book);
if (CollectionUtils.isNotEmpty(bookList)) {
redisTemplate.opsForHash().put(BOOK_LIST_KEY,BOOK_AUTHOR_KEY_PREFIX + book.getAuthor(),bookList);
}
return bookList;
}
}
下面通過debug方式啟動項目,看下注入的RedisTemplate
根據圖-1就能看出來自定義的Redis配置生效了,有興趣可以和默認狀態(tài)下注入的RedisTemplate對比一下匈仗。然后我們使用Redis圖形化管理工具看下Redis中存儲的數(shù)據瓢剿,如下:
我使用是hash存放數(shù)據,hash key就是前綴加author悠轩,value就是ArrayList间狂,然后具體的泛型是Book,根據上面這個圖就可以很直觀的知道Redis存儲的具體的類型火架,這也是為什么一般使用JSON存放數(shù)據的原因鉴象。
當然也是可以使用StringRedisTemplate操作Redis的,自己可以轉成JSON串然何鸡,取出存儲的值之后自己再反序列化成具體的類型就行了纺弊。
二、spring cache使用Redis
上面我們是直接操作Redis對數(shù)據存取骡男,這種方式可能會略顯麻煩淆游,因為會有很多和Redis操作相關的代碼。其實我們還可以通過使用spring cache來管理緩存隔盛,關于spring cache可以查看spring官方的文檔犹菱。接下來我們看下如何在項目中使用spring cache。
首先是要配置spring cache使用的類型吮炕,即Redis腊脱,當然也可以使用其他類型。
spring.cache.type=redis
spring.cache.redis.cache-null-values=false
spring.cache.redis.time-to-live=60s
spring.cache.cache-names=redis-cache
spring.cache.redis.use-key-prefix=true
spring.cache.redis.key-prefix=cache_key_prefix_
配置是否緩存null值来屠、緩存過期時間虑椎、緩存名稱震鹉、是否使用key前綴等等俱笛,然后我們通過代碼測試一下效果,自己寫一個controller和service:
/**
* 根據author查詢传趾,用于測試spring cache
* @param author
* @return
*/
@PostMapping("/query/{author}")
public List<Book> queryByAuthor(@PathVariable("author") String author) {
LOGGER.info(">>>> query books by author name, author name={} <<<<",author);
return redisService.queryByAuthorName(author);
}
@Cacheable("redis_cache_value")
@Override
public List<Book> queryByAuthorName(String author) {
LOGGER.info(">>>> query by author name, author name={} <<<<",author);
List<Book> bookList = bookMapper.queryByAuthorName(author);
return bookList;
}
啟動項目迎膜,調用接口測試一下代碼,然后我們看下Redis中數(shù)據是如何存儲的:
根據這個結果感覺有點問題浆兰,雖然數(shù)據確實存儲到Redis中了磕仅,但是我并不存在我們指定的cache名稱,我期望的是db8下游一個名為"redis_cache_value"的緩存名稱簸呈,然后這個緩存下存放"cache_key_prefix_ypc"榕订。不過根據"cache_key_prefix_ypc"也可以猜測出,具體的可以應該就是我們查詢的參數(shù)值蜕便。這里還有一個問題劫恒,就是存儲數(shù)據的格式問題,默認使用的二進制,但是一般我們還是希望用JSON存儲两嘴,所以我們需要配置丛楚,所以在RedisConfig代碼里面需要定義一個cacheManager,代碼如下:
/**
* 自定義cache manager 這個配置主要配合spring cache使用憔辫,和使用redisTemplate操作沒有關系
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
LOGGER.info(">>>> custom redis cache manager start <<<<");
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
// 指定key value的序列化類型
RedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
RedisSerializationContext.SerializationPair<Object> jsonSerializationPair = RedisSerializationContext.SerializationPair
.fromSerializer(jsonSerializer);
RedisSerializationContext.SerializationPair<String> stringSerializationPair = RedisSerializationContext.SerializationPair
.fromSerializer(RedisSerializer.string());
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().serializeKeysWith(stringSerializationPair)
.serializeValuesWith(jsonSerializationPair)
.disableCachingNullValues().entryTtl(Duration.ofSeconds(100));
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,redisCacheConfiguration);
return redisCacheManager;
}
這段代碼主要定義了緩存的key和value的序列化策略趣些,以及緩存過期時間,當然還可以指定其他配置贰您,這個可以百度或者看下具體代碼坏平,這里只做簡單配置。
然后修改下的service代碼锦亦,將緩存的key顯性設置為查詢參數(shù)的值功茴,代碼如下:
@Cacheable(value = "redis_cache_value",key = "#author")
@Override
public List<Book> queryByAuthorName(String author) {
LOGGER.info(">>>> query by author name, author name={} <<<<",author);
List<Book> bookList = bookMapper.queryByAuthorName(author);
return bookList;
}
上面說到指定的緩存的名稱不存在,即"redis_cache_value"以及我們配置文件指定的"redis-cache"都沒生效孽亲,但是不指定又會報錯坎穿,自己暫時還沒有找到解決方案,有了解的小伙伴可以指點一下返劲。key可以自己定義玲昧,甚至自定義配置key generator都可以,這里就不細述了孵延。
我們再次啟動項目測試一下,看下我們的配置有沒有生效亲配,結果如下:
這個結果有點意外,居然沒想到指定的緩存名稱生效了犬钢,但是指定緩存key的前綴不在了,我的感覺就是配置cache manager時候思灰,里面配置的configuration覆蓋了原有項目配置文件的配置,具體是不是這樣感興趣的可以自己去測試一下洒疚。
關于spring boot中Redis作為緩存就到這里吧,還要去加班呢(難過…)油湖,文章略顯粗糙請見諒啊巍扛,確實趕時間,畢竟兩周沒更新了乏德。這次的代碼我上傳到我的github了撤奸。
最后:自己在微信開了一個個人號:
超超學堂
,都是自己之前寫過的一些文章,另外關注還有Java免費自學資料寂呛,歡迎大家關注怎诫。