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

avatar 2020年1月31日12:09:30 评论 66 views

本文介绍 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
 */
@Component
public 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
 */
@Service
public 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
@EnableCaching
public 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
 */
@Service
public 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 也是方便测试

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

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: