spring boot中使用Redis作為緩存

最近這幾天一直在加班心情很是郁悶桨武,項目中遇到的問題很多,加上很多遺留代碼镐作,最主要的是對業(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.png

根據圖-1就能看出來自定義的Redis配置生效了,有興趣可以和默認狀態(tài)下注入的RedisTemplate對比一下匈仗。然后我們使用Redis圖形化管理工具看下Redis中存儲的數(shù)據瓢剿,如下:


圖-2.png

我使用是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ù)據是如何存儲的:


圖-3.png

根據這個結果感覺有點問題浆兰,雖然數(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都可以,這里就不細述了孵延。
我們再次啟動項目測試一下,看下我們的配置有沒有生效亲配,結果如下:

圖-4.png

這個結果有點意外,居然沒想到指定的緩存名稱生效了犬钢,但是指定緩存key的前綴不在了,我的感覺就是配置cache manager時候思灰,里面配置的configuration覆蓋了原有項目配置文件的配置,具體是不是這樣感興趣的可以自己去測試一下洒疚。
關于spring boot中Redis作為緩存就到這里吧,還要去加班呢(難過…)油湖,文章略顯粗糙請見諒啊巍扛,確實趕時間,畢竟兩周沒更新了乏德。這次的代碼我上傳到我的github了撤奸。


最后:自己在微信開了一個個人號:超超學堂,都是自己之前寫過的一些文章,另外關注還有Java免費自學資料寂呛,歡迎大家關注怎诫。

二維碼.jpg

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贷痪,隨后出現(xiàn)的幾起案子幻妓,更是在濱河造成了極大的恐慌,老刑警劉巖劫拢,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肉津,死亡現(xiàn)場離奇詭異,居然都是意外死亡舱沧,警方通過查閱死者的電腦和手機妹沙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熟吏,“玉大人距糖,你說我怎么就攤上這事∏K拢” “怎么了悍引?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長帽氓。 經常有香客問我趣斤,道長,這世上最難降的妖魔是什么黎休? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任浓领,我火速辦了婚禮,結果婚禮上势腮,老公的妹妹穿的比我還像新娘联贩。我一直安慰自己,他們只是感情好嫉鲸,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布撑蒜。 她就那樣靜靜地躺著歹啼,像睡著了一般玄渗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狸眼,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天藤树,我揣著相機與錄音,去河邊找鬼岁钓。 笑死,一個胖子當著我的面吹牛品嚣,可吹牛的內容都是我干的翰撑。 我是一名探鬼主播啊央,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瓜饥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了宪潮?” 一聲冷哼從身側響起趣苏,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤拦键,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后萄金,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體媚朦,經...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年孙乖,在試婚紗的時候發(fā)現(xiàn)自己被綠了唯袄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜗帜。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡厅缺,死狀恐怖宴偿,靈堂內的尸體忽然破棺而出窄刘,到底是詐尸還是另有隱情舷胜,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布欺矫,位于F島的核電站穆趴,受9級特大地震影響遇汞,放射性物質發(fā)生泄漏。R本人自食惡果不足惜络它,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一化戳、第九天 我趴在偏房一處隱蔽的房頂上張望埋凯。 院中可真熱鬧,春花似錦掠廓、人聲如沸甩恼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塞关。三九已至子巾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椰于,已是汗流浹背仪搔。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工烤咧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笛谦。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓饥脑,卻偏偏與公主長得像懦冰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子笋颤,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內容