zoukankan      html  css  js  c++  java
  • spring boot项目10:Redis-直接使用

    JAVA 8

    Spring Boot 2.5.3

    Redis server v=4.0.9 (单机,默认配置)

    ---

    授人以渔:

    1、Spring Boot Reference Documentation

    This document is also available as Multi-page HTML, Single page HTML and PDF.

    有PDF版本哦,下载下来!

    2、Spring Data Redis

    无PDF,网页上可以搜索。

    目录

    基本介绍

    试验1:使用CommandLineRunner测试 StringRedisTemplate

    试验2:使用CommandLineRunner测试 RedisTemplate

    试验3:使用RedisTemplate测试opsForValue()操作 类型为String的值

    试验4:使用RedisTemplate测试opsForValue()操作 类型为Long的值

    试验5:使用RedisTemplate测试opsForSet()操作 类型为String的无序集合

    缓存,可以用来提高获取数据的速度,在计算机里面,就有各种缓存。

    在软件工程中,也会用到各种缓存,其中,Redis是其中的佼佼者,可以有效提高用户获取数据的效率。

    本文演示使用Spring Boot程序操作Redis。

    基本介绍

    在S.B.中,依赖下面的JAR包即可使用Redis:

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

    Redis 服务器 默认端口号 6379,上面的包引入后,默认使用的是 localhost:6379。

    本文使用的是虚拟机的Redis服务器,因此,要做配置:

    #
    # Redis
    # mylinux 是虚拟机的本地域名,配置到 hosts文件中
    spring.redis.host=mylinux
    spring.redis.port=6379

    在S.B.中,还有更多以 spring.redis. 开头的配置——可以去官文查找:

    # 部分 spring.redis.* 配置
    spring.redis.username
    spring.redis.password
    spring.redis.url
    
    spring.redis.connect-timeout # 连接超时时间
    spring.redis.timeout # 读数据超时时间
    
    spring.redis.ssl # SSL支持
    
    spring.redis.jedis.* # 使用jedis客户端时的配置
    spring.redis.lettuce.* # 使用Lettuce客户端时的配置

    S.B.使用缓存时,也有一些Redis相关配置,本文暂不介绍。

    添加上面依赖后,S.B.应用 会将下面的一些 和Redis相关的 Bean 添加到 Spring容器中:

    # 常用
    redisTemplate
    stringRedisTemplate
    reactiveRedisTemplate
    reactiveStringRedisTemplate
    
    # 其它
    RedisAutoConfiguration
    RedisProperties
    redisConnectionFactory
    
    RedisReactiveAutoConfiguration
    
    redisCustomConversions
    redisConverter
    redisKeyValueAdapter
    redisKeyValueTemplate
    
    # 默认的Lettuce客户端,,可以更改为Jedis客户端
    LettuceConnectionConfiguration
    lettuceClientResources
    

    一些Type的签名:StringRedisTemplate 类 继承了 RedisTemplate

    public class RedisAccessor implements InitializingBean {
    }
    
    public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
    }
    
    // RedisTemplate 的一个泛型类
    public class StringRedisTemplate extends RedisTemplate<String, String> {
    }

    Reactive版本的类似(暂未使用):

    public interface ReactiveRedisOperations<K, V> {
    }
    
    public class ReactiveRedisTemplate<K, V> implements ReactiveRedisOperations<K, V> {
    }
    
    public class ReactiveStringRedisTemplate extends ReactiveRedisTemplate<String, String> {
    }

    RedisTemplate类 是其中的重点:来自博客园

    其中的 opsForXXX 函数用来获取 不同数据类型的操作对象——Value、Set、List、ZSet、Hash、Geo、HyperLogLog、Stream。

    注,其中的 opsForCluster 用来进行集群操作,咱不清楚——不同的数据存入不同主机?

    RedisTemplate类 中还有几个 RedisSerializer属性,用来对 键、值 等进行序列化:

    默认的序列化对象在使用时存在一些问题,需要做更改,后文介绍。

    	private boolean enableDefaultSerializer = true;
    	private @Nullable RedisSerializer<?> defaultSerializer;
        
    	@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
    	@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
    	@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
    	@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
    	private RedisSerializer<String> stringSerializer = RedisSerializer.string();

    试验1:使用CommandLineRunner测试 StringRedisTemplate

    测试代码:key、value 都是 字符串String

    @Component
    @Order(1)
    @Slf4j
    class TestRunner1 implements CommandLineRunner {
    
    	@Autowired
    	private StringRedisTemplate stringRedisTemplate;
    	
    	@Override
    	public void run(String... args) throws Exception {
    		log.info("测试 StringRedisTemplate类:stringRedisTemplate={}", stringRedisTemplate);
    		stringRedisTemplate.opsForValue().set("test1", "str1");
    		stringRedisTemplate.opsForValue().set("test2", "str2", Duration.ofSeconds(30));
    		stringRedisTemplate.opsForValue().set("test3", "str3", 20);
    		stringRedisTemplate.opsForValue().set("test4", "str4", 60, TimeUnit.SECONDS);
    		
    		String s1 = (String) stringRedisTemplate.opsForValue().get("test1");
    		Long l1 = stringRedisTemplate.getExpire("test1");
    		String s2 = (String) stringRedisTemplate.opsForValue().get("test2");
    		Long l2 = stringRedisTemplate.getExpire("test2");
    		String s3 = (String) stringRedisTemplate.opsForValue().get("test3");
    		Long l3 = stringRedisTemplate.getExpire("test3");
    		String s4 = (String) stringRedisTemplate.opsForValue().get("test4");
    		Long l4 = stringRedisTemplate.getExpire("test4");
    		
    		log.info("执行结果:");
    		log.info("s1={}, l1={}", s1, l1);
    		log.info("s2={}, l2={}", s2, l2);
    		log.info("s3={}, l3={}", s3, l3);
    		log.info("s4={}, l4={}", s4, l4);
    		
    		log.info("del test1: {}", stringRedisTemplate.delete("test1"));
    		log.info("del test2: {}", stringRedisTemplate.delete("test2"));
    		log.info("del test3: {}", stringRedisTemplate.delete("test3"));
    		log.info("del test4: {}", stringRedisTemplate.delete("test4"));
    	}
    	
    }

    执行结果:测试代码正常执行来自博客园

    试验2:使用CommandLineRunner测试 RedisTemplate

    测试代码:来自博客园

    @Component
    @Order(2)
    @Slf4j
    class TestRunner2 implements CommandLineRunner {
    
    //	@Autowired // 添加泛型参数后,不可用,需改为 @Resource
    	@Resource
    	private RedisTemplate<String, Object> redisTemplate;
    	
    	@Override
    	public void run(String... args) throws Exception {
    		log.info("测试 RedisTemplate类:redisTemplate={}", redisTemplate);
    		
    		redisTemplate.opsForValue().set("test1", "str1");
    		redisTemplate.opsForValue().set("test2", "str2", Duration.ofSeconds(30));
    		redisTemplate.opsForValue().set("test3", "str3", 20);
    		redisTemplate.opsForValue().set("test4", "str4", 60, TimeUnit.SECONDS);
    		
    		String s1 = (String) redisTemplate.opsForValue().get("test1");
    		Long l1 = redisTemplate.getExpire("test1");
    		String s2 = (String) redisTemplate.opsForValue().get("test2");
    		Long l2 = redisTemplate.getExpire("test2");
    		
    		// 异常发生!
    		// 改造 RedisTemplate 的序列化器 后,可以执行了,见 {@link RedisConfig}
    		String s3 = (String) redisTemplate.opsForValue().get("test3");
    		Long l3 = redisTemplate.getExpire("test3");
    		
    		String s4 = (String) redisTemplate.opsForValue().get("test4");
    		Long l4 = redisTemplate.getExpire("test4");
    		
    		log.info("执行结果:");
    		log.info("s1={}, l1={}", s1, l1);
    		log.info("s2={}, l2={}", s2, l2);
    		log.info("s3={}, l3={}", s3, l3);
    		log.info("s4={}, l4={}", s4, l4);
    		
    		log.info("del test1: {}", redisTemplate.delete("test1"));
    		log.info("del test2: {}", redisTemplate.delete("test2"));
    		log.info("del test3: {}", redisTemplate.delete("test3"));
    		log.info("del test4: {}", redisTemplate.delete("test4"));
    	}

    执行结果1:发生异常,一个序列化问题。

    # 取一行
    # 执行 String s3 = (String) redisTemplate.opsForValue().get("test3"); 时
    Caused by: org.springframework.core.serializer.support.SerializationFailedException: 
    Failed to deserialize payload. Is the byte array a result of corresponding serialization 
    for DefaultDeserializer?; nested exception is java.io.StreamCorruptedException: 
    invalid stream header: 00000000

    前文提到,RedisTemplate类 有几个序列化器,此时,更改 RedisTemplate类 默认的序列化器即可。

    	/**
    	 * RedisTemplate配置:序列化器
    	 * @author ben
    	 * @date 2021-08-23 15:01:41 CST
    	 * @param redisConnectionFactory
    	 * @return
    	 */
    	@Bean
    	public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    		// 1.创建模板
    		RedisTemplate template = new RedisTemplate();
    		// 2.关联redisConnectionFactory
    		template.setConnectionFactory(redisConnectionFactory);
    		// 3.创建序列化器对象
    		GenericToStringSerializer serializer = new GenericToStringSerializer(Object.class);
    		
    		// 4.设置 value、key 的转化格式——序列化器
    		template.setValueSerializer(serializer);
    		template.setKeySerializer(new StringRedisSerializer());
    		
    		template.afterPropertiesSet();
    		
    		return template;
    	}

    再次执行,一切正常:

    附1:序列化器类型,RedisSerializer接口 下有多个 实现类

    RedisTemplate Bean默认的序列化器是什么呢?

    # StringRedisTemplate 全是 StringRedisSerializer
    log.info("序列化器:{}
    {}
    {}
    {}", stringRedisTemplate.getKeySerializer(), stringRedisTemplate.getValueSerializer(),
        stringRedisTemplate.getHashKeySerializer(), stringRedisTemplate.getHashValueSerializer());
    序列化器:org.springframework.data.redis.serializer.StringRedisSerializer@ef718de
    org.springframework.data.redis.serializer.StringRedisSerializer@ef718de
    org.springframework.data.redis.serializer.StringRedisSerializer@ef718de
    org.springframework.data.redis.serializer.StringRedisSerializer@ef718de
    
    # RedisTemplate 全是 JdkSerializationRedisSerializer
    log.info("序列化器:{}
    {}
    {}
    {}", redisTemplate.getKeySerializer(), redisTemplate.getValueSerializer(),
        redisTemplate.getHashKeySerializer(), redisTemplate.getHashValueSerializer());
    序列化器:org.springframework.data.redis.serializer.JdkSerializationRedisSerializer@67fb5025
    org.springframework.data.redis.serializer.JdkSerializationRedisSerializer@67fb5025
    org.springframework.data.redis.serializer.JdkSerializationRedisSerializer@67fb5025
    org.springframework.data.redis.serializer.JdkSerializationRedisSerializer@67fb5025

    RedisTemplate 下的 JdkSerializationRedisSerializer 对 解析 这种带offset的发生异常。来自博客园

    试验3:使用RedisTemplate测试opsForValue()操作 类型为String的值

    注,先使用RedisTemplate默认序列化器。

    两个接口:

    /redis/value/setValueStr 设置值

    /redis/value/getValueStr 获取值

    public boolean setValue(@RequestParam String key, @RequestParam String value, @RequestParam Long timeout) {
    }
    
    public String getValueStr(@RequestParam String key) {
    }

    测试结果:一切正常。

    测试key值:str1

    不正常的是什么呢?使用redis-cli时,无法获取设置的 key=str1 的数据。

    额,这又是一个 序列化器的问题!解决方法同试验2——配置RedisTemplate 的序列化器。

    配置后,测试完使用 redis-cli 获取测试的 key=str1 的结果如下:来自博客园

    符合预期了。

    试验4:使用RedisTemplate测试opsForValue()操作 类型为Long的值

    注,先使用RedisTemplate默认序列化器。

    三个接口:来自博客园

    /redis/value/setValueLong 设置值

    /redis/value/getValueLong 获取值

    /redis/value/incrementValueLong 增加值

    public boolean setValue(@RequestParam String key, @RequestParam Long value, @RequestParam Long timeout) {
    }
    
    public Long getValueLong(@RequestParam String key) {
    }
    
    public Long incrementValueLong(@RequestParam String key, @RequestParam Long delta) {
    }

    测试key值:long1

    测试结果:

    设置值 正常,,但redis-cli 看到的 key值不正常(上面介绍过)
    获取值 不正常,没有返回值,没有key
    增加值 发生异常(见下方)

    增加值 时的异常信息如下:

    [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is 
    org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is 
    io.lettuce.core.RedisCommandExecutionException: ERR value is not an integer or out of range] with root cause
    
    io.lettuce.core.RedisCommandExecutionException: ERR value is not an integer or out of range
    	at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:137) 
        ~[lettuce-core-6.1.4.RELEASE.jar:6.1.4.RELEASE]

    是的,又是序列化器的问题

    改为 前面修改序列化器后的 RedisTemplate测试:一切符合预期。来自博客园

    试验5:使用RedisTemplate测试opsForSet()操作 类型为String的无序集合

    set 是 Redis支持的无需集合,除了 存取数据,还支持做 交集、并集、差集等计算。

    本试验仅做了 添加、获取、pop 操作的测试。

    三个接口:

    /redis/set/add 添加若干个元素

    /redis/set/getAll 获取所有元素

    /redis/set/popOne 弹出一个元素来自博客园

    测试key值:set1

    测试结果:

    未发生异常。

    使用默认序列化器时,redis-cli 客户端看到的 键值是错误的,入前面一样更改了 序列化器即可。

    更改后 redis-cli 检查结果:

    注:

    使用 Set<Object> set = redisTemplate.opsForSet().members(key); 无法获取 符合预期的 全体元素,

    改为使用下面的 可行了:randomMembers、size 两个函数

    		// 第二种获取元素的方式
    		List<Object> list = redisTemplate.opsForSet().randomMembers(key, redisTemplate.opsForSet().size(key));
    		list.forEach(obj->{
    			retset.add((String) obj);
    		});

    试验2、3、4、5 的各个接口的源码如下:

    两个类的源码
    # AddToSetDTO.java
    /**
     * 添加元素到缓存集合
     * @author ben
     * @date 2021-08-23 15:42:34 CST
     */
    @Data
    public class AddToSetDTO {
    
    	private String key;
    	
    	private String[] values;
    	
    }
    
    # RedisController.java
    /**
     * HTTP操作Redis
     * @author ben
     * @date 2021-08-23 13:39:07 CST
     */
    @RestController
    @RequestMapping(value="/redis")
    @Slf4j
    public class RedisController {
    
    	@Resource
    	private RedisTemplate<String, Object> redisTemplate;
    	
    	// -------------value-------------
    	
    	/**
    	 * 前缀: /value
    	 */
    	private final static String PATH_VALUE = "/value/";
    	
    	/**
    	 * 设置String值
    	 * @author ben
    	 * @date 2021-08-23 14:14:33 CST
    	 * @param key 非空
    	 * @param value 非空
    	 * @param timeout 必须大于0,过期秒数
    	 * @return 成功返回true
    	 */
    	@PostMapping(value=PATH_VALUE + "/setValueStr")
    	public boolean setValue(@RequestParam String key, @RequestParam String value, @RequestParam Long timeout) {
    		if (!(StringUtils.hasText(key) && StringUtils.hasText(value) && timeout != null && timeout > 0)) {
    			throw new RuntimeException("参数错误");
    		}
    		
    		log.info("key={}, value={}, timeout={}", key, value, timeout);
    		
    		// 注意,第三个参数是 Duration
    		redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(timeout));
    		return redisTemplate.hasKey(key);
    	}
    	
    	/**
    	 * 获取String值
    	 * @author ben
    	 * @date 2021-08-23 14:15:12 CST
    	 * @param key
    	 * @return 值
    	 */
    	@GetMapping(value=PATH_VALUE + "/getValueStr")
    	public String getValueStr(@RequestParam String key) {
    		if (!StringUtils.hasText(key)) {
    			throw new RuntimeException("参数错误");
    		}
    
    		log.info("key={}, ttl={} seconds", key, redisTemplate.getExpire(key));
    		
    		return (String) redisTemplate.opsForValue().get(key);
    	}
    
    	/**
    	 * 设置长整型
    	 * @author ben
    	 * @date 2021-08-23 14:15:29 CST
    	 * @param key 非空
    	 * @param value 非null
    	 * @param timeout 必须大于0
    	 * @return
    	 */
    	@PostMapping(value=PATH_VALUE + "/setValueLong")
    	public boolean setValue(@RequestParam String key, @RequestParam Long value, @RequestParam Long timeout) {
    		if (!(StringUtils.hasText(key) && value != null && timeout != null && timeout > 0)) {
    			throw new RuntimeException("参数错误");
    		}
    		
    		redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(timeout));
    		return redisTemplate.hasKey(key);
    	}
    	
    	/**
    	 * 获取长整型值
    	 * @author ben
    	 * @date 2021-08-23 14:15:56 CST
    	 * @param key
    	 * @return
    	 */
    	@GetMapping(value=PATH_VALUE + "/getValueLong")
    	public Long getValueLong(@RequestParam String key) {
    		if (!StringUtils.hasText(key)) {
    			throw new RuntimeException("参数错误");
    		}
    		
    		log.debug("key={}, ttl={} seconds", key, redisTemplate.getExpire(key));
    		
    		Long retval = null;
    		Object val = redisTemplate.opsForValue().get(key);
    		if (String.class.equals(val.getClass())) {
    			retval = Long.valueOf(String.valueOf(val));
    		} else {
    			log.warn("Redis中没有key={}", key);
    		}
    		
    		return retval;
    	}
    	
    	/**
    	 * 给长整型增加值
    	 * @author ben
    	 * @date 2021-08-23 14:16:09 CST
    	 * @param key
    	 * @param delta 非null,可正,可负
    	 * @return
    	 */
    	@PostMapping(value=PATH_VALUE + "/incrementValueLong")
    	public Long incrementValueLong(@RequestParam String key, @RequestParam Long delta) {
    		if (!(StringUtils.hasText(key) && delta != null )) {
    			throw new RuntimeException("参数错误");
    		}
    		
    		log.info("key={}, delta={}", key, delta);
    		
    		// 发生异常:io.lettuce.core.RedisCommandExecutionException: 
    		// ERR value is not an integer or out of range
    		// 改造 redisTemplate 的序列化器 见 {@link RedisConfig}
    		return redisTemplate.opsForValue().increment(key, delta);
    	}
    	
    	// -------------set-------------
    
    	/**
    	 * 前缀:set/
    	 */
    	private final static String PATH_SET = "/set/";
    	
    	/**
    	 * 添加到集合
    	 * @author ben
    	 * @date 2021-08-23 15:55:10 CST
    	 * @param dto
    	 * @return
    	 */
    	@PostMapping(value=PATH_SET + "/add")
    	public Long addToSet(@RequestBody AddToSetDTO dto) {
    		String key = dto.getKey();
    		String[] values = dto.getValues();
    		if (!StringUtils.hasText(key) || Objects.isNull(values)) {
    			throw new RuntimeException("参数错误");
    		}
    		
    		if (values.length == 0) {
    			return 0L;
    		}
    		
    		return redisTemplate.opsForSet().add(key, values);
    	}
    	
    	/**
    	 * 获取集合全部元素
    	 * @author ben
    	 * @date 2021-08-23 15:55:24 CST
    	 * @param key
    	 * @return
    	 */
    	@GetMapping(value=PATH_SET + "/getAll")
    	public Set<String> getAllSet(@RequestParam String key) {
    		Set<String> retset = new HashSet<>(32);
    		
    		// 返回数据不符合预期,TODO
    		Set<Object> set = redisTemplate.opsForSet().members(key);
    		Optional.of(set).ifPresent(item -> {
    			// 仅执行一次
    //			System.out.println("item=" + item);
    			retset.add(String.valueOf(item));
    		});
    		
    		return retset;
    	}
    	
    	/**
    	 * 删除并返回集合中一个随机元素
    	 * @author ben
    	 * @date 2021-08-23 15:55:31 CST
    	 * @param key
    	 * @return
    	 */
    	@PostMapping(value=PATH_SET + "/popOne")
    	public String popSet(@RequestParam String key) {
    		return (String) redisTemplate.opsForSet().pop(key);
    	}
    	
    }
    

    说明210825-2002

    还有hash、list、HyperLogLog、ZSet等,本文暂不介绍了。来自博客园

    参考文档

    1、redis在java中设置了缓存值为什么在redis-cli获取不到

    2、为什么要用Redis?Redis为什么这么快?

    内存数据库、单线程+IO多路复用,文章中内容更精彩。

    3、RedisTemplate的key、value默认序列化器问题

    4、

  • 相关阅读:
    ASP.NET程序中常用的三十三种代码【转】
    BTree,BTree,B+Tree,B*Tree都是什么
    调用Google地图
    JS特效总结
    2005 加入博客园
    .net Windows服务程序安装与安装程序的制作
    七夕,爱似流年
    JS+CSS仿魔兽游戏进入进度条特效
    常用正则表达式
    Google翻译网站添加Google翻译,让老外也看的懂你的网站
  • 原文地址:https://www.cnblogs.com/luo630/p/15177541.html
Copyright © 2011-2022 走看看