spring boot中使用Redis作为缓存

最近这几天一直在加班心情很是郁闷,项目中遇到的问题很多,加上很多遗留代码,最主要的是对业务也不熟悉,工作经常事倍功半。今天写这个也是因为工作中遇到了和Redis相关的问题,自己单独写了一个demo,也算是一个工作总结吧。spring boot中使用Redis地方可以作为缓存、可以存储session,今天主要说下缓存方面相关的知识,内容主要分为两部分:一是直接操作Redis,即显性操作Redis;二是隐式调用,主要是使用spring cache结合Redis。

一、显性操作Redis

其实之前自己做spring boot项目操作Redis使用的是自己封装的工具类,但是本质上使用的还是redisTemplate,但是这次项目中发现有使用redisTemplate也有stringRedisTemplate。说一下自己遇到的问题,自己在service注入redisTemplate,并向Redis存储数据报错,异常信息是类型转换错误,List不能转成string。后来才知道项目专门有Redis的模块,而默认使用的序列化策略是StringRedisSerializer,自己只有使用Gson将List转成json串存放,然后取出的时候再反序列化转成List,当然也可以自己再重新配置一个RedisTemplate。RedisTemplate默认采取的序列化策略是jdk的序列化方式,但是数据存放到Redis后数据不直观,因此一般会使用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设置属性值之后执行,然后会设置序列化方式,默认的话是使用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序列化策略的区别,建议百度一下,这里不细述了。而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没有再查询数据库,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配置生效了,有兴趣可以和默认状态下注入的RedisTemplate对比一下。然后我们使用Redis图形化管理工具看下Redis中存储的数据,如下:


图-2.png

我使用是hash存放数据,hash key就是前缀加author,value就是ArrayList,然后具体的泛型是Book,根据上面这个图就可以很直观的知道Redis存储的具体的类型,这也是为什么一般使用JSON存放数据的原因。
当然也是可以使用StringRedisTemplate操作Redis的,自己可以转成JSON串然,取出存储的值之后自己再反序列化成具体的类型就行了。

二、spring cache使用Redis

上面我们是直接操作Redis对数据存取,这种方式可能会略显麻烦,因为会有很多和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中数据是如何存储的:


图-3.png

根据这个结果感觉有点问题,虽然数据确实存储到Redis中了,但是我并不存在我们指定的cache名称,我期望的是db8下游一个名为"redis_cache_value"的缓存名称,然后这个缓存下存放"cache_key_prefix_ypc"。不过根据"cache_key_prefix_ypc"也可以猜测出,具体的可以应该就是我们查询的参数值。这里还有一个问题,就是存储数据的格式问题,默认使用的二进制,但是一般我们还是希望用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显性设置为查询参数的值,代码如下:

    @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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容