Springboot——redis入门

版权声明:版权没有,盗用不究 https://blog.csdn.net/liman65727/article/details/84727017

前言

互联网应用中,NoSQL已经广泛应用。Redis是一种内存数据库,Redis可以在1S内完成10万次的操作,针对查询比较大的应用,使用redis作为缓存,可以极大程度提高应用运行效率,为了总结这篇博客,之前已经花了时间进行了redis入门级别的学习,弄清了redis底层的相关操作命令,这个在后面进行总结。但是redis的内容不止有这些,还有redis事务,redis的事务机制可以有效保证在高并发的场景下数据的一致性,针对redis事务的操作,这篇博客后续再进行更新,目前这篇博客只能对spring和springboot中对redis的操作进行总结。

spring中针对redis的操作

redis是一种键值对数据库。但是不管如何,其依旧是一种数据库,spring针对数据访问的操作其实都大同小异,提供对应的ConnectionFactory接口,然后获取对应的connection对象,之后利用对应的connection对象进行数据访问的操作。

但是针对redis的数据访问又有些不同,这个也是因为redis本身的数据类型所致

 大体上与其他的数据访问层没有二异,但是我们操作的时候有三种可以选择的连接对象,JedisConnection、JedisClusterConnection、DefaultStringRedisConnection三个,这个也是因为redis本身的特性和功能决定的。spring同时为我们屏蔽了烦人的操作,最终提供的只有一个RedisTemplate对象,我们在实际开发中只需要与这个对象打交道就可以了。

RedisTemplate对象

这个和JPA中的template对象一样。spring已经帮我们做了很多了,RedisTemplate是一个强大的类,它会自动从RedisConnectionFactory中获取连接对象,然后执行对应的Redis命令,在最后还会关闭Redis的连接,基本不需要我们做什么。但是需要理清楚JedisConnectionFactory,JedisPoolConfig,RedisTemplate三个对象之间的关系。JedisConnectionFactory中需要注入JedisPoolConfig,RedisTemplate对象中需要注入JedisConnectionFactory。因为RedisTemplate已经自动完成了连接对象的获取和销毁,所以这里我们不需要与连接对象打交道。

实例代码:

package com.learn.chapter07.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
@Configuration
public class RedisConfig {
    private JedisConnectionFactory connectionFactory;

    public RedisConfig() {
        connectionFactory = null;
    }

    @Bean(name = "redisConnectionFactory")
    public RedisConnectionFactory initConnectionFactory() {
        if (this.connectionFactory != null) {
            return this.connectionFactory;
        }
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // 最大空闲数
        poolConfig.setMaxIdle(50);
        // 最大连接数
        poolConfig.setMaxTotal(100);
        // 最大等待毫秒数
        poolConfig.setMaxWaitMillis(2000);
        // 创建Jedis连接工厂
        JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
        // 配置Redis连接服务器
        RedisStandaloneConfiguration rsc = connectionFactory.getStandaloneConfiguration();
        rsc.setHostName("127.0.0.1");
        rsc.setPort(6379);
//        rsc.setPassword(RedisPassword.of("123456"));
        this.connectionFactory = connectionFactory;
        return connectionFactory;
    }

    @Bean(name="redisTemplate")
    public RedisTemplate<Object, Object> initRedisTemplate() {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(initConnectionFactory());

        //自动初始化String字符串的序列化转换器,这里直接获取。
        RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();

        //设置字符串序列化器,这样spring就可以把Redis的key当做字符串处理了
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(stringRedisSerializer);
        return redisTemplate;
    }
}

针对RedisConfig的测试

    /**
     * 入门测试
     */
    @Test
    public void testRedisConfig(){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(RedisConfig.class);
        RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);
		redisTemplate.opsForValue().set("key1", "value1");
		redisTemplate.opsForHash().put("hash", "field", "hvalue");
//        useSessionCallback(redisTemplate);
//		useRedisCallback(redisTemplate);
        ctx.close();
    }

上述代码只是测试了key-value的数据结构和hash两种数据结构。最终的在redis中数据显示如下:

 出现这种情况,其实是由于Redis是一种基于字符串存储的NoSQL,而Java是基于对象的语言,对象是无法存储到redis的数据库中的,但是Java还是个叫Serializable的标记接口,实现了这个接口的对象都能完成序列化,对象序列化之后就会转换成二进制字符串,这样redis就可以将这些类按照字符串存储,但是Java本身的序列化机制转换的并不是正常的字符串。毕竟实现Serializable接口得到的字符串和实现toString()方法得到的字符串是不一样的(不知道这么理解对不对)。

扫描二维码关注公众号,回复: 4363965 查看本文章

spring提供了对应的序列化器,并且实现了几个序列化器

针对redis数据库中不同的数据类型,spring提供了不同的序列化器,这些序列化器都注入到了RedisTemplate中,同时RedisTemplate还预留了一个默认的序列化器,整个RedisTemplate中的序列化器的属性如下所示:

在之前,没有配置任何序列化器的时候,RedisTemplate会自动使用JdkSerializationRedisSerializer序列化器,这样就产生了不正确的字符串,因此,为了让Redis的键和值以普通的字符串形式保存,我们只需要换一个序列化器就可以了。因此原来的代码中就多了以下几行

        //自动初始化String字符串的序列化转换器,这里直接获取。
        RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();

        //设置字符串序列化器,这样spring就可以把Redis的key当做字符串处理了
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(stringRedisSerializer);

SessionCallBack和RedisCallBack接口

上述的实例,的两个操作,其实并不是在一个会话操作中完成的,可以看到前后执行的结果如下图所示:

 RedisTemplate在操作redis的时候,会先从Factory中获取一个连接,然后执行对应的Redis命令,再关闭这条连接,执行后面的hash操作的时候,会从Factory中获取另一个连接,然后执行命令,再关闭连接。这样明显存在资源浪费。为了克服这个问题,Spring为我们提供了RedisCallback接口和SessionCallBack接口,同时为了减少序列化转换的问题,Spring还专门提供了SpringRedisTemplate对象,这个类继承至RedisTemplate,但是StringRedisTemplate只是提供了字符串的操作而已,对于复杂的Java对象还需要自行处理,这个都是后话了。

RedisCallBack和SessionCallBack两个接口的区别并不是很大,RedisCallBack相比SessionCallBack,前者更加接近底层,但是他能改写一些底层的东西,在需要改写底层的东西可以使用它。SessionCallBack接口则更加友好。两者实例如下:

 @Test
    public void testRedisCallBack(){
        //1.获取redisTemplate.
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(RedisConfig.class);
        RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);

        //内部类的形式执行redisCallback
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                redisConnection.set("key2".getBytes(),"RedisCallBack".getBytes());
                redisConnection.hSet("hash2".getBytes(),"field".getBytes(),"redisCallBackhash".getBytes());
                return null;
            }
        });

        ctx.close();
    }

    @Test
    public void testSessionCallBack(){
        //1.获取redisTemplate.
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(RedisConfig.class);
        RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);

        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                redisOperations.opsForValue().set("keyForSessionCallback","sessionCallBackValue".getBytes());
                redisOperations.opsForHash().put("hashForSessionCallback","sessionCallbackField","sessionCallbackValue");
                return null;
            }
        });
    }

在SessionCallBack使用的时候,使用了spring针对redis数据类型进行操作的接口,具体如下:

 RedisTemplate获取对应的接口的操作也比较简单,如下所示:

 spring boot中使用redis

在spring中我们需要手动配置ConnectionFactory,但是在springboot中,已经为我们自动注入了这个对象,因此我们在springboot中只需要指定redis的配置即可:

之前针对序列化的问题,已经专门探讨过,在springboot中为了修改RedisTemplate对象的序列化属性,需要利用该对象的后置生命周期函数,修改该对象的序列化属性。

如下所示:

package com.learn.chapter07;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

import javax.annotation.PostConstruct;

@SpringBootApplication(scanBasePackages = "com.learn.chapter07")
public class Chapter07Application {

	@Autowired
	private RedisTemplate redisTemplate;

	@PostConstruct
	public void init(){
		initRedisTemplate();
	}

	private void initRedisTemplate(){
		RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
		redisTemplate.setKeySerializer(stringSerializer);
		redisTemplate.setHashKeySerializer(stringSerializer);
	}


	public static void main(String[] args) {
		SpringApplication.run(Chapter07Application.class, args);
	}
}

其中的@PostConstruct注解指定的后置bean函数,完成了序列化器的设置。

下面是一下操作redis三种常用数据结构的代码。这里直接贴出

package com.learn.chapter07.redis.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;

import java.util.*;

/**
 * autor:liman
 * mobilNo:15528212893
 * mail:[email protected]
 * comment:
 */
@Controller
@RequestMapping(value="/redis")
public class RedisController {

    @Autowired
    private RedisTemplate redisTemplate = null;

    @Autowired
    private StringRedisTemplate stringRedisTemplate = null;

    @RequestMapping("/stringAndHash")
    @ResponseBody
    public Map<String,Object> redisStringAndHash(){

        redisTemplate.opsForValue().set("keyForSpringboot","springbootredisvalue");
        //这里没设置序列化器,只是使用了Jdk的序列化器,所以redis保存的不是指定的类型,因此不能运算
        redisTemplate.opsForValue().set("int_key","1");

        stringRedisTemplate.opsForValue().set("int","1");
        stringRedisTemplate.opsForValue().increment("int",1);

        //通过原始的jedis操作redis,减法操作RedisTemplate不支持,所以需要通过底层的jedis进行操作。
        Jedis jedis = (Jedis)stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
        jedis.decr("int");

        //构建散列类型的数据,准备存入
        Map<String,String> hash = new HashMap<>();
        hash.put("field1","value1");
        hash.put("field2","value2");

        //存入一个散列的数据类型
        stringRedisTemplate.opsForHash().putAll("hash",hash);
        stringRedisTemplate.opsForHash().put("hash","field1","value1");

        //利用BoundHashOperations对象操作hash数据类型,可以连续对同一个散列数据类型进行操作
        BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash");
        //删除两个指定的字段
        hashOps.delete("field1","field2");
        //新增一个字段
        hashOps.put("field5","value5");

        Map<String,Object> result = new HashMap<String,Object>();
        result.put("success",true);
        return result;
    }

    @RequestMapping("/list")
    @ResponseBody
    public Map<String,Object> ListOperation(){

        stringRedisTemplate.opsForList().leftPushAll("list1","v2","v4","v6","v8","v10");
        stringRedisTemplate.opsForList().rightPushAll("list2","v1","v3","v5","v7","v9");

        //绑定list2链表操作
        BoundListOperations listOps = stringRedisTemplate.boundListOps("list2");
        //从右边弹出一个元素
        Object element = listOps.rightPop();
        Object posElement = listOps.index(2);
        listOps.leftPush("v0");

        Long size = listOps.size();
        List elements = listOps.range(0,size-2);
        for(Object obj : elements){
            System.out.println(obj);
        }
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("success",true);
        return result;
    }

    @RequestMapping("/set")
    @ResponseBody
    public Map<String,Object> setOperation(){
        stringRedisTemplate.opsForSet().add("set1","v1","v1","v2","v3","v4","v5","v6");
        stringRedisTemplate.opsForSet().add("set2","v2","v4","v6","v8");

        //绑定set1集合
        BoundSetOperations setOps = stringRedisTemplate.boundSetOps("set1");
        //增加两个元素
        setOps.add("v7","v8");
        setOps.remove("v1","v7");
        //返回所有元素
        Set set1 = setOps.members();
        Long size = setOps.size();

        //求交集
        Set inter = setOps.intersect("set2");
        for(Object obj:inter){
            System.out.println(obj);
        }
        //求交集,并用新集合inter保存
        setOps.intersectAndStore("set2","inter");
        //求差集
        Set diff = setOps.diff("set2");
        for(Object obj:inter){
            System.out.println(obj);
        }
        //求差集,并用新集合diff保存
        setOps.diffAndStore("set2","diff");

        Set union = setOps.union("set2");
        for(Object obj:inter){
            System.out.println(obj);
        }
        //求并集,并用新集合union保存
        setOps.unionAndStore("set2","union");

        Map<String,Object> result = new HashMap<String,Object>();
        result.put("success",true);
        return result;
    }

    @RequestMapping("/zset")
    @ResponseBody
    public Map<String,Object> zSetOperation(){

        //存储分数的集合
        Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
        for(int i=1;i<=9;i++){
            double score = i*0.1;
            ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<String>("value"+i,score);
            typedTupleSet.add(typedTuple);
        }

        //往有序集合中插入元素
        stringRedisTemplate.opsForZSet().add("zset1",typedTupleSet);
        BoundZSetOperations<String,String> boundZSetOperations = stringRedisTemplate.boundZSetOps("zset1");
        boundZSetOperations.add("value10",0.26);
        Set<String> setRange = boundZSetOperations.range(1,6);

        //将分数排序获取有序集合
        Set<String> setScore = boundZSetOperations.rangeByScore(0.2,0.6);
        //定义取值范围
        RedisZSetCommands.Range range = new RedisZSetCommands.Range();
        //大于value3
        range.gt("value3");
        //小于等于value8
        range.lte("value8");

        //按值排序
        Set<String> setLex = boundZSetOperations.rangeByLex(range);
        //删除元素
        boundZSetOperations.remove("value9","value2");
        //求分数
        Double score = boundZSetOperations.score("value8");
        Set<ZSetOperations.TypedTuple<String>> rangSet = boundZSetOperations.rangeWithScores(1,6);
        Set<String> reverseSet = boundZSetOperations.reverseRange(2,8);


        Map<String,Object> result = new HashMap<String,Object>();
        result.put("success",true);
        return result;
    }

}

说明:这里直接采用的是Controller形式进行的测试,当然依旧还是可以用测试脚本进行测试。

总结

该篇文章只是针对spring中对redis的操作进行了一个简单的总结,针对redis事务的操作却没有进一步总结,针对redis的数据结构,也没有进行详细的归纳,后面会有相关的博客进行补充。同时该篇博客依旧参考了《深入浅出 spring boot 2.X》一书。

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/84727017