SpringBoot2.x整合Redis两种使用方式

本文介绍 SpringBoot 整合 Redis,常见的两种使用方式:redisTemplate 和 @Cacheable 注解。

redisTemplate 是常见 spring-data-redis 提供的用法,一般我们会封装一个 RedisUtil 工具类来方便我们调用。

@Cacheable 和 @CacheEvict 注解也是 Spring 提供的注解,我们知道注解一般是基于 AOP 实现,通过在注解方法前后进行一些操作,简化开发者的代码。

下面会通过一个增删改查介绍两种用法。

代码地址:https://github.com/saysky/redis-demo

一、SpringBoot 整合 Redis

1、Maven 依赖

pom.xml

<!--    Redis    --><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId>    <version>2.2.4.RELEASE</version></dependency><!--   json     --><dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.60</version></dependency>

这里 springboot 版本为 2.2.4.RELEASE,spring-boot-starter-data-redis 和 boot 保持同一版本

使用 fastjson 作为json工具

2、配置文件

application.yml

spring:  redis:    database: 0# 单机版配置    host: 127.0.0.1    port: 6379# 集群配置#    cluster:#      max-redirects: 1#      nodes:#        - 127.0.0.1:7010#        - 127.0.0.1:7011#        - 127.0.0.1:7012#    password:    lettuce:      pool:        #最大连接数据库连接数,设 0 为没有限制        max-active: 8        #最大等待连接中的数量,设 0 为没有限制        max-idle: 8        #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。        max-wait: -1        #最小等待连接中的数量,设 0 为没有限制        min-idle: 0    timeout: 1000

二、用法一:RedisUtil

1、新建 RedisUtil.java

package com.liuyanzhao.redis.utils; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Component; import java.util.Map;import java.util.Set;import java.util.concurrent.TimeUnit; /** * @author 言曌 * @date 2020-01-29 14:11 */@Componentpublic class RedisUtil {     @Autowired    private StringRedisTemplate redisTemplate;      // Key(键),简单的key-value操作     /**     * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。     *     * @param key     * @return     */    public long ttl(String key) {        return redisTemplate.getExpire(key);    }     /**     * 实现命令:expire 设置过期时间,单位秒     *     * @param key     * @return     */    public void expire(String key, long timeout) {        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);    }     /**     * 实现命令:INCR key,增加key一次     *     * @param key     * @return     */    public long incr(String key, long delta) {        return redisTemplate.opsForValue().increment(key, delta);    }      /**     * 实现命令: key,减少key一次     *     * @param key     * @return     */    public long decr(String key, long delta) {        if (delta < 0) {//            throw new RuntimeException("递减因子必须大于0");            del(key);            return 0;        }        return redisTemplate.opsForValue().increment(key, -delta);    }     /**     * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key     */    public Set<String> keys(String pattern) {        return redisTemplate.keys(pattern);    }     /**     * 实现命令:DEL key,删除一个key     *     * @param key     */    public void del(String key) {        redisTemplate.delete(key);    }     /**     * 根据通配符批量删除     *     * @param key     */    public void delByKeys(String key) {        Set<String> keys = redisTemplate.keys(key+"*");        redisTemplate.delete(keys);    }      // String(字符串)     /**     * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)     *     * @param key     * @param value     */    public void set(String key, String value) {        redisTemplate.opsForValue().set(key, value);    }     /**     * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)     *     * @param key     * @param value     * @param timeout (以秒为单位)     */    public void set(String key, String value, long timeout) {        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);    }     /**     * 实现命令:GET key,返回 key所关联的字符串值。     *     * @param key     * @return value     */    public String get(String key) {        return (String) redisTemplate.opsForValue().get(key);    }    // Hash(哈希表)     /**     * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value     *     * @param key     * @param field     * @param value     */    public void hset(String key, String field, Object value) {        redisTemplate.opsForHash().put(key, field, value);    }     /**     * 实现命令:HGET key field,返回哈希表 key中给定域 field的值     *     * @param key     * @param field     * @return     */    public String hget(String key, String field) {        return (String) redisTemplate.opsForHash().get(key, field);    }     /**     * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。     *     * @param key     * @param fields     */    public void hdel(String key, Object... fields) {        redisTemplate.opsForHash().delete(key, fields);    }     /**     * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。     *     * @param key     * @return     */    public Map<Object, Object> hgetall(String key) {        return redisTemplate.opsForHash().entries(key);    }    // List(列表)     /**     * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头     *     * @param key     * @param value     * @return 执行 LPUSH命令后,列表的长度。     */    public long lpush(String key, String value) {        return redisTemplate.opsForList().leftPush(key, value);    }     /**     * 实现命令:LPOP key,移除并返回列表 key的头元素。     *     * @param key     * @return 列表key的头元素。     */    public String lpop(String key) {        return (String) redisTemplate.opsForList().leftPop(key);    }     /**     * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。     *     * @param key     * @param value     * @return 执行 LPUSH命令后,列表的长度。     */    public long rpush(String key, String value) {        return redisTemplate.opsForList().rightPush(key, value);    }}

后面我们都会在需要使用 Redis 的地方注入 RedisUtil 就行

2、基本操作

package com.liuyanzhao.redis.service.impl; import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.serializer.SerializerFeature;import com.liuyanzhao.redis.entity.User;import com.liuyanzhao.redis.repository.UserRepository;import com.liuyanzhao.redis.service.UserService;import com.liuyanzhao.redis.utils.RedisUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils; import java.util.Optional; /** * Redis缓存方法一:通过 redisTemplate 操作 * * @author 言曌 * @date 2020-01-29 14:23 */@Servicepublic class UserServiceImpl implements UserService {     @Autowired    private UserRepository userRepository;     @Autowired    private RedisUtil redisUtil;     public static final String REDIS_USER_ID_KEY_PREFIX = "user::id-";     @Override    public User findById(Long id) {        // 从 redis 查询        String value = redisUtil.get(REDIS_USER_ID_KEY_PREFIX + id);        if (!StringUtils.isEmpty(value)) {            User user = JSON.parseObject(value, User.class);            if (user != null) {                return user;            }        }         // redis 没有,从数据库查询        Optional<User> optional = userRepository.findById(id);        User user = optional.isPresent() ? optional.get() : null;         // 将对象存储到 redis        redisUtil.set(REDIS_USER_ID_KEY_PREFIX + id, JSON.toJSONString(user, SerializerFeature.WriteClassName));        return user;    }     @Override    public void insert(User user) {        // 新增        userRepository.save(user);    }     @Override    public void update(User user) {        // 更新        userRepository.save(user);         // 删除 redis 的        redisUtil.del(REDIS_USER_ID_KEY_PREFIX + user.getId());    }     @Override    public void deleteById(Long id) {        //注意:必须先删除数据库的,再删除 redis 的        // 删除数据库的        userRepository.deleteById(id);         //删除 redis 的        redisUtil.del(REDIS_USER_ID_KEY_PREFIX + id);    }}

简单说下:

这里我们都以Redis 的 String 数据类型为例,将对象通过 fastjson 序列化为 JSON 字符串,然后存储到 Redis 中,然后查询出来的时候,再通过 fastjson 将 JSON 字符串转成 java 对象。

(1) 查询操作:先查询 Redis,如果Redis没有则再查询数据库,并添加到 Redis

(2) 删除操作:需要先删除数据库数据,再删除Redis中的

(3) 更新操作:  更新数据库数据,删除 Redis 数据,当然也可以再添加 Redis 记录

使用 Redis Desktop Manager 来查看,可以看到数据格式为 String 类型的 JSON 格式,里面加了 @type,

如果不想要 @type,可以在序列化的时候去掉 SerializerFeature.WriteClassName,即替换 JSON.toJSONString(user, SerializerFeature.WriteClassName)); 为 JSON.toJSONString(user));

三、用法二:使用注解 @Cacheabele

1、新建 FastJsonRedisSerializer.java

因为我们方法二使用的 fastjson 来序列化和反序列化,为了兼容方法二的序列化结果,使两种Redis操作方式能完全兼容,这里我们会设置序列化方式为 fastjson

重写 RedisSerializer 类,重写序列化和反序列化方法

package com.liuyanzhao.redis.config; import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;import com.alibaba.fastjson.serializer.SerializerFeature;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.Charset; /** * @author 言曌 * @date 2020-01-29 16:23 */public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {      public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");     private Class<T> clazz;     // 解决 autoType is not support 问题    static {        ParserConfig.getGlobalInstance().addAccept("com.liuyanzhao.redis");//        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);    }     public FastJsonRedisSerializer(Class<T> clazz) {        super();        this.clazz = clazz;    }     @Override    public byte[] serialize(T t) throws SerializationException {        if (t == null) {            return new byte[0];        }        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);    }     @Override    public T deserialize(byte[] bytes) throws SerializationException {        if (bytes == null || bytes.length <= 0) {            return null;        }        String str = new String(bytes, DEFAULT_CHARSET);        return JSON.parseObject(str, clazz);    }}

注意为了解决 autoType is not support 问题,需要添加,需要修改下面包名,改成你的项目包名就行,只要使用缓存注解的类所在包属于该包就行

 static {      ParserConfig.getGlobalInstance().addAccept("com.liuyanzhao.redis");}

2、新建 RedisConfig.java

需要开启Spring的缓存功能,添加 @EnableCaching 注解,一般在启动类上添加,这里我们直接在配置类上添加就行

然后需要设置序列化和反序列化的类

package com.liuyanzhao.redis.config; import org.springframework.cache.annotation.CachingConfigurerSupport;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.cache.RedisCacheConfiguration;import org.springframework.data.redis.serializer.RedisSerializationContext; import java.time.Duration; /** * 缓存配置类 * 用于注解的缓存配置,如 @Cacheable 的序列化配置 * * 说明: * 我们 redisUtils 序列化方式采用 json序列化 * @Cacheable 默认序列化方式为 二进制的 * 两个不能混用,为了解决这个问题,这里设置 @Cacheable 默认序列化方式为 json * @author 言曌 * @date 2020-01-29 16:13 */@Configuration@EnableCachingpublic class RedisConfig extends CachingConfigurerSupport {     /**     * 设置 redis 数据默认过期时间     * 设置@cacheable 序列化方式     *     * @return     */    @Bean    public RedisCacheConfiguration redisCacheConfiguration() {        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(30));        return configuration;    }   }

3、基本操作

也是增删改查

package com.liuyanzhao.redis.service.impl; import com.liuyanzhao.redis.entity.User;import com.liuyanzhao.redis.repository.UserRepository;import com.liuyanzhao.redis.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service; import java.util.Optional; /** * Redis缓存方法二:通过 @Cacheable、@CacheEvict 注解实现 操作,需要开启缓存 @EnableCaching * * @author 言曌 * @date 2020-01-29 14:23 */@Servicepublic class UserServiceImpl2 implements UserService {     @Autowired    private UserRepository userRepository;     @Override    @Cacheable(value = "user", key = "'id-'+#id")    public User findById(Long id) {        Optional<User> optional = userRepository.findById(id);        User user = optional.isPresent() ? optional.get() : null;        return user;    }     @Override    public void insert(User user) {        // 新增        userRepository.save(user);    }     @Override    @CacheEvict(value = "user", key = "'id-'+#user.id")    public void update(User user) {        // 更新        userRepository.save(user);    }     @Override    @CacheEvict(value = "user", key = "'id-'+#id")    public void deleteById(Long id) {        // 删除数据库的        userRepository.deleteById(id);    }}

这里说明下:

@Cacheable 注解是先根据 key 去查询 Redis 中是否有这个 key,如果有则直接返回。

如果没有则执行方法体,最后将方法返回内容添加到 Redis 中,key 为上面那个key

@CacheEvict 注解是先执行方法体,然后根据 key 去 Redis 中删除

其实还有一个 @CachePut 注解,这里觉得没必要用,用这两个注解足矣

四、代码地址

完整代码:https://github.com/saysky/redis-demo

该 demo 数据库用的是 H2,相对 MySQL 而言不需要创建数据库,适合做测试使用

ORM 框架用的是 SpringDataJPA 也是方便测试

完整代码结构如下,大家可以自行去下载代码

0
分享到:

评论0

请先

没有账号? 忘记密码?