Redisspringboot

spring boot中使用Redis作为缓存

2019-05-12  本文已影响0人  非典型_程序员

最近这几天一直在加班心情很是郁闷,项目中遇到的问题很多,加上很多遗留代码,最主要的是对业务也不熟悉,工作经常事倍功半。今天写这个也是因为工作中遇到了和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
上一篇 下一篇

猜你喜欢

热点阅读