SpringBoot中如何正确使用Redis(详细介绍,原理讲解,企业版)

1.引入Redis依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2.配置Redis的连接信息(application.yml)

实际开发中有两个一个是开发环境application-dev,一个是生产环境application-prod。

spring:
  redis:
    database: 0           # Redis服务器数据库
    host: 127.0.0.1       # Redis服务器地址
    port: 6379            # Redis服务器连接端口
    password: 123456      # Redis服务器连接密码(默认为空)
    timeout: 6000         # Redis连接超时时间(毫秒)

3.创建RedisConfig主要是通过@Bean注入RedisTemplate。

一般我们配置RedisTemplate的时候,配置类一大堆东西,接下来我从最简单的配置到最终的配置依次测试和演示为什么需要这么配置的原因。

(1)最简单的配置,直接new RedisTemplate通过@Bean注入,写个简单的controller测试。

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(){
        return new RedisTemplate();
    }
}
@RestController
@RequestMapping("/test")
public class RedisController {

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/aaa")
    public String testA(){
        redisTemplate.opsForValue().set("test","123456");
        return "success";
    }
}

接下来我么直接启动项目来看。

很明显直接报错,说我们需要一个Redis的连接工厂,所以接下来我们去配置Redis的连接工厂。

(2) 配置Reids的连接工厂。如何去配置。
稍微分析一下源码,这个工厂是什么,我们应该如何如给他赋值。

上面这个是RedisTemplate的set赋值方法,可见需要一个RedisConnectionFactory,这个又是什么呢?继续点进去。

很明显这是个接口,那我们要找他的实现类,

可见一共有两个实现类 JedisConnectionFactory 和 LettuceConnectionFactory 。

好的,有耐心分析到这说明你已经有分析源码的动力了!!!!!!!!!太深了咱也没必要挖。

接下来看我们如何去配置

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

这里不知道大家有没有疑问,为什么RedisConnectionFactory作为形参传过来,我们直接set就行,我们并没有给它创建对象,按理说它不应该是null吗?实际上这个方法是Spring自动调用的,参数呢也是Spring自动去到Spring的容器中寻找然后自动装配上去的,所以此处不为空,那我们怎么证明呢?直接打个断点去看。

此处我们可以直接看出它不是null,而且可以看出它默认使用的工厂LettuceConnectionFactory,
所以我们不用担心直接使用即可。

(3)我们配置完我们工厂后又会有新问题:乱码问题怎么解决。

首先我们去随便存储一个key-value到我们的Redis数据库中。代码:

@RestController
@RequestMapping("/test")
public class RedisController {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @RequestMapping("/aaa")
    public String testA(){
        redisTemplate.opsForValue().set("test","123456");
        return "success";
    }
}

结果

很明显,出现了奇怪的东?首先它并不是乱码,他只是存储的时候前面保存了序列化的元数据部分,跟它存储key-value的序列化方式有关,我们没有设置的情况下,它默认使用JdkserializationRedisSerializer,这个就会保留元数据,要想不保存元数据就需要改变它默认的序列化方式,其常用的有两种StringRedisSerializerJackson2RedisSerializer,这两种都不会保留序列的元数据,他们两个有什么区别的,咱直接试一下就知道。
假设先使用第一种StringRedisSerialzer,设置key和value都用这个。代码如下:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());//设置key存储的序列化方式
        redisTemplate.setValueSerializer(new StringRedisSerializer());//设置value存储的序列化方式
        return redisTemplate;
    }
}

我给它存储一个User对象。

@RestController
@RequestMapping("/test")
public class RedisController {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @RequestMapping("/aaa")
    public String testA(){
        User user = new User();
        user.setName("张三");
        user.setAge(22);
        redisTemplate.opsForValue().set("user",user);
        return "success";
    }
}

结果:

很显然,直接报错,说我们User不能被转换为String,因为我们设置value序列化方式为String。
接下来我们给它转换成JSON字符串进行存储试试看。引入阿里巴巴提供的FastJSON。

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.71</version>
        </dependency>
@RestController
@RequestMapping("/test")
public class RedisController {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @RequestMapping("/aaa")
    public String testA(){
        User user = new User();
        user.setName("张三");
        user.setAge(22);
        redisTemplate.opsForValue().set("user", JSON.toJSONString(user));
        return "success";
    }
}

再来看结果:
这会就直接可以,但是它存进去的数据是JSON数据,并不是JSON字符串。

那么Jackson2RedisSerializer这个序列化方式的直接存储user对象,是可以直接存的不会报错,结果跟上面这个一样,但是如果你存JSON字符串进去,结果就是JSON字符串。结果:

一般来说JSON字符串都是带'\'这玩意的,当然上面也不是我们想要的,所以总结一下。

如果是StringRedisSerializer,传JSON字符串,如果是Jackson2JsonRedisSerializer,直接传对象。
 

虽然讲了上面两种的区别,但是往往这也是最烦人的,我喜欢唯一,所以二者必须要选一种,哪怕是公司的话也是选择一种,我们就选择StringRedisSerializer。
最终代码:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

4.真的需要RedisConfig吗?

虽然上面讲了那么多,但是,只是让大家了解一下原理,或者公司的写法可能就是这样的,但是为什么可以不用写这个呢?首先SpringBoot集成了Redis以后,会有一个RedisAutoConfiguration的自动配置类。

可以很明显的看出,它已经为我们提供了两个现成的RedisTemplate和StringRedisTemplate。

其中前者我们不可能用,因为之前讲过了,它没有设置序列化方式,会出现“乱码”问题。

那就需要自己配置也就是我们上面的3讲的。那为什么可用不用自己配置呢,这就是讲的StringRedisTemplate,它要求我们设置的key和value都必须是String类型,那为什么它不会出现乱码呢?我们直接点进去看一看。

可以从源码看出来它实际上是继承RedisTemplate的基础上在创建对象的时候又进行了序列化的设置,设置为RedisSerialiizer.string()。这个点进去实际上就是StringRedisSerializer.

所以我们上面的3实际上就是StringRedisTemplate。那公司开发时为什么还要自己配置呢?我觉得可能是沿用n多年前公司的写法,但是当时具体的含义又不是很清楚,所以到现在还保持着。

5.实际公司开发怎么选择?

如果是老项目,我们就没必要去纠结这个问题,我们知道它为什么这么写就行了,你不可能说给它改了,人家那种写法也不错,其实只要统一要求开发规范就行,大家都遵守这个进行开发。

如果是新项目,当然我们可以和leader去沟通这个问题,其实问题不大,只要你讲清楚就行。

第一种的话,它不会进行编译检查,如果你直接value是个对象而没有转换成JSON字符串,那么运行的时候直接会报错,这种就很不好。
第二种的话,我们就强制要求了,你传对象过来,我编译器就报错,你就必须给我转成JSON字符串进行存储,就不会出现上面的问题,这个规范就很强制。

我们知道上面的区别和好处啊,具体使用看情况,你别头铁不顾一切你就要用你自己的方式,到最后领导找你再让你改。但是我们一定要知道,分清楚。

其次就是我们一般还会封装一个Redis的工具类,虽然set存储其实也就一句话,还是封装一下更统一开发规范和要求。更高的提升效率。

6.封装Redis的工具类RedisUtil

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return 陈工失败
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                Boolean boo = redisTemplate.expire(key, time, TimeUnit.SECONDS);
                return boo != null && boo;
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            Boolean boo = redisTemplate.hasKey(key);
            return boo != null && boo;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    public boolean del(String... key) {
        try {
            if (key != null && key.length > 0) {
                if (key.length == 1) {
                    redisTemplate.delete(key[0]);
                } else {
                   redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));

                }
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据前缀删除key
     * @param prex
     */
    public boolean deleteByPrex(String prex) {
        try {
            prex = prex+"**";
            Set<String> keys = redisTemplate.keys(prex);
            redisTemplate.delete(keys);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    //============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        try{
            return redisTemplate.opsForValue().get(key);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 加锁
     *
     * @param key         键
     * @param value         值
     * @param releaseTime 锁过期时间 秒
     * @return 结果
     */
    public boolean lock(String key, Object value, long releaseTime) {
        try {
            Boolean boo = redisTemplate.opsForValue().setIfAbsent(key, value, releaseTime, TimeUnit.SECONDS);
            return boo != null && boo;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 解锁
     *
     * @param key key
     */
    public boolean unLock(String key) {
        try {
            Boolean boo = redisTemplate.delete(key);
            return boo != null && boo;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 向指定list的队列头部批量添加value
     *
     * @param key key
     */
    public boolean rightPush(String key, String value) {
        try {
            Long aLong = redisTemplate.opsForList().rightPush(key, value);
            return aLong != 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 向指定list的队列头部批量添加value
     *
     * @param key key
     */
    public boolean leftPush(String key, String value) {
        try {
            Long aLong = redisTemplate.opsForList().leftPush(key, value);
            return aLong != 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 移除并获取指定list中(队列-头部/尾部)第一个元素
     *
     * @param key key
     */
    public Object leftPop(String key) {
        try {
            return redisTemplate.opsForList().leftPop(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }


    /**
     * 指定key+!
     *
     * @param key key
     */
    public Object incr(String key,Long num) {
        try {
            return redisTemplate.opsForValue().increment(key,num);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    /**
     * 根据参数查询相关KEY集合
     */
    public Set<String> getKeyListByStr(String key){
        key = key+"**";
        return redisTemplate.keys(key);
    }


}

虽然有一些简答的没必要,但是一切都是为了统一开发规范,我们给类加个@Compinent注解,让我们的@Autowired注解生效,其次我们可以直接通过@Autowired注入RedisUtil进行方法调用。

@RestController
@RequestMapping("/test")
public class RedisController {
    @Autowired
    private RedisUtil redisUtil;
    @RequestMapping("/aaa")
    public String testA(){
        User user = new User();
        user.setName("张三");
        user.setAge(22);
        redisUtil.set("test","8888888888888");
        return "success";
    }
}

有点累了,后续出常用的操作及其应用场景。

  • 31
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值