# springbootRedis **Repository Path**: superlth/springbootRedis ## Basic Information - **Project Name**: springbootRedis - **Description**: springboot与redis的集合 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-07-30 - **Last Updated**: 2022-08-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # springbootRedis springboot与redis的集合 ## 概述 本文内容主要 - 关于spring-redis - 关于redis的key设计 - redis的基本数据结构 - 介绍redis与springboot的整合 - sringboot中的redistemplate的使用 之前看了很多博客,大都都只是粗略的介绍,这里想要记录的全面一些,也算是一个学习的过程 首发于我的个人博客:[janti的个人博客](http://www.janti.cn/) ## 关于spring-redis spring-data-redis针对jedis提供了如下功能: 1. 连接池自动管理,提供了一个高度封装的“RedisTemplate”类 2. 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口 ValueOperations:简单K-V操作 SetOperations:set类型数据操作 ZSetOperations:zset类型数据操作 HashOperations:针对map类型的数据操作 ListOperations:针对list类型的数据操作 3. 提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations: BoundValueOperations BoundSetOperations BoundListOperations BoundSetOperations BoundHashOperations 4. 将事务操作封装,有容器控制。 5. 针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer) JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。 StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。 JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】 OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】 **如果你的数据需要被第三方工具解析,那么数据应该使用StringRedisSerializer而不是JdkSerializationRedisSerializer。** ## 关于key的设计 ### key的存活时间: 无论什么时候,只要有可能就利用key超时的优势。一个很好的例子就是储存一些诸如临时认证key之类的东西。当你去查找一个授权key时——以OAUTH为例——通常会得到一个超时时间。 这样在设置key的时候,设成同样的超时时间,Redis就会自动为你清除。 ### 关系型数据库的redis 1: 把表名转换为key前缀 如, tag: 2: 第2段放置用于区分区key的字段--对应mysql中的主键的列名,如userid 3: 第3段放置主键值,如2,3,4...., a , b ,c 4: 第4段,写要存储的列名 例:user:userid:9:username ## Redis的数据类型 ### String字符串 - string是redis最基本的类型,一个key对应一个value。 - string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。 - string类型是Redis最基本的数据类型,一个键最大能存储512MB。 - [String类型的操作参考](http://www.runoob.com/redis/redis-strings.html) ### 链表 - redis列表是简单的字符串列表,排序为插入的顺序。列表的最大长度为2^32-1。 - redis的列表是使用链表实现的,这意味着,即使列表中有上百万个元素,增加一个元素到列表的头部或尾部的操作都是在常量的时间完成。 - 可以用列表获取最新的内容(像帖子,微博等),用ltrim很容易就会获取最新的内容,并移除旧的内容。 - 用列表可以实现生产者消费者模式,生产者调用lpush添加项到列表中,消费者调用rpop从列表中提取,如果没有元素,则轮询去获取,或者使用brpop等待生产者添加项到列表中。 - [List类型的操作参考](http://www.runoob.com/redis/redis-lists.html) ### 集合 - redis集合是无序的字符串集合,集合中的值是唯一的,无序的。可以对集合执行很多操作,例如,测试元素是否存在,对多个集合执行交集、并集和差集等等。 - 我们通常可以用集合存储一些无关顺序的,表达对象间关系的数据,例如用户的角色,可以用sismember很容易就判断用户是否拥有某个角色。 - 在一些用到随机值的场合是非常适合的,可以用 srandmember/spop 获取/弹出一个随机元素。 同时,使用@EnableCaching开启声明式缓存支持,这样就可以使用基于注解的缓存技术。注解缓存是一个对缓存使用的抽象,通过在代码中添加下面的一些注解,达到缓存的效果。 - [Set类型的操作参考](http://www.runoob.com/redis/redis-sets.html) ### ZSet 有序集合 - 有序集合由唯一的,不重复的字符串元素组成。有序集合中的每个元素都关联了一个浮点值,称为分数。可以把有序看成hash和集合的混合体,分数即为hash的key。 - 有序集合中的元素是按序存储的,不是请求时才排序的。 - [ZSet类型的操作类型](http://www.runoob.com/redis/redis-sorted-sets.html) ### Hash-哈希 - redis的哈希值是字符串字段和字符串之间的映射,是表示对象的完美数据类型。 - 哈希中的字段数量没有限制,所以可以在你的应用程序以不同的方式来使用哈希。 - [Hash类型的操作参考](http://www.runoob.com/redis/redis-hashes.html) ## springboot 与redis的整合 ### pom文件 依赖如下: ``` org.springframework.boot spring-boot-starter-parent 1.5.1.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-data-redis ``` ### application.properties ``` # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=0 ``` ### redisTemplate的配置 新建一个redisConfig类,进行相关bean的配置: ``` package com.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; 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.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @author janti * reids 相关bean的配置 */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { /** * 选择redis作为默认缓存工具 * @param redisTemplate * @return */ @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager rcm = new RedisCacheManager(redisTemplate); return rcm; } /** * retemplate相关配置 * @param factory * @return */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); // 配置连接工厂 template.setConnectionFactory(factory); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); // 值采用json序列化 template.setValueSerializer(jacksonSeial); //使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); // 设置hash key 和value序列化模式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jacksonSeial); template.afterPropertiesSet(); return template; } /** * 对hash类型的数据操作 * * @param redisTemplate * @return */ @Bean public HashOperations hashOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForHash(); } /** * 对redis字符串类型数据操作 * * @param redisTemplate * @return */ @Bean public ValueOperations valueOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForValue(); } /** * 对链表类型的数据操作 * * @param redisTemplate * @return */ @Bean public ListOperations listOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForList(); } /** * 对无序集合类型的数据操作 * * @param redisTemplate * @return */ @Bean public SetOperations setOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForSet(); } /** * 对有序集合类型的数据操作 * * @param redisTemplate * @return */ @Bean public ZSetOperations zSetOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForZSet(); } } ``` - spring-redis中使用了RedisTemplate来进行redis的操作,通过泛型的K和V设置键值对的对象类型。这里使用了string作为key的对象类型,值为Object。 - 对于Object,spring-redis默认使用了jdk自带的序列化,不推荐使用默认了。所以使用了json的序列化方式 - 对spring-redis对redis的五种数据类型也有支持 - HashOperations:对hash类型的数据操作 - ValueOperations:对redis字符串类型数据操作 - ListOperations:对链表类型的数据操作 - SetOperations:对无序集合类型的数据操作 - ZSetOperations:对有序集合类型的数据操作 ### redis操作的工具类 ``` package com.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Date; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @Component public class RedisService { @Autowired private RedisTemplate redisTemplate; /** * 默认过期时长,单位:秒 */ public static final long DEFAULT_EXPIRE = 60 * 60 * 24; /** * 不设置过期时长 */ public static final long NOT_EXPIRE = -1; public boolean existsKey(String key) { return redisTemplate.hasKey(key); } /** * 重名名key,如果newKey已经存在,则newKey的原值被覆盖 * * @param oldKey * @param newKey */ public void renameKey(String oldKey, String newKey) { redisTemplate.rename(oldKey, newKey); } /** * newKey不存在时才重命名 * * @param oldKey * @param newKey * @return 修改成功返回true */ public boolean renameKeyNotExist(String oldKey, String newKey) { return redisTemplate.renameIfAbsent(oldKey, newKey); } /** * 删除key * * @param key */ public void deleteKey(String key) { redisTemplate.delete(key); } /** * 删除多个key * * @param keys */ public void deleteKey(String... keys) { Set kSet = Stream.of(keys).map(k -> k).collect(Collectors.toSet()); redisTemplate.delete(kSet); } /** * 删除Key的集合 * * @param keys */ public void deleteKey(Collection keys) { Set kSet = keys.stream().map(k -> k).collect(Collectors.toSet()); redisTemplate.delete(kSet); } /** * 设置key的生命周期 * * @param key * @param time * @param timeUnit */ public void expireKey(String key, long time, TimeUnit timeUnit) { redisTemplate.expire(key, time, timeUnit); } /** * 指定key在指定的日期过期 * * @param key * @param date */ public void expireKeyAt(String key, Date date) { redisTemplate.expireAt(key, date); } /** * 查询key的生命周期 * * @param key * @param timeUnit * @return */ public long getKeyExpire(String key, TimeUnit timeUnit) { return redisTemplate.getExpire(key, timeUnit); } /** * 将key设置为永久有效 * * @param key */ public void persistKey(String key) { redisTemplate.persist(key); } } ``` ### redis的key工具类 ``` package com.util; /** * redisKey设计 */ public class RedisKeyUtil { /** * redis的key * 形式为: * 表名:主键名:主键值:列名 * * @param tableName 表名 * @param majorKey 主键名 * @param majorKeyValue 主键值 * @param column 列名 * @return */ public static String getKeyWithColumn(String tableName,String majorKey,String majorKeyValue,String column){ StringBuffer buffer = new StringBuffer(); buffer.append(tableName).append(":"); buffer.append(majorKey).append(":"); buffer.append(majorKeyValue).append(":"); buffer.append(column); return buffer.toString(); } /** * redis的key * 形式为: * 表名:主键名:主键值 * * @param tableName 表名 * @param majorKey 主键名 * @param majorKeyValue 主键值 * @return */ public static String getKey(String tableName,String majorKey,String majorKeyValue){ StringBuffer buffer = new StringBuffer(); buffer.append(tableName).append(":"); buffer.append(majorKey).append(":"); buffer.append(majorKeyValue).append(":"); return buffer.toString(); } } ``` ## 如何使用? ### 测试代码 新建一个实体类: ``` package com.domain; public class UserVo { public static final String Table = "t_user"; private String name; private String address; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "UserVo{" + "name='" + name + '\'' + ", address='" + address + '\'' + ", age=" + age + '}'; } } ``` 再新建一个测试类: ``` package com.config; import com.domain.UserVo; import com.service.RedisService; import com.util.RedisKeyUtil; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.*; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; import java.util.Set; import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest public class RedisConfigTest { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisTemplate redisTemplate; @Resource private ValueOperations valueOperations; @Autowired private HashOperations hashOperations; @Autowired private ListOperations listOperations; @Autowired private SetOperations setOperations; @Autowired private ZSetOperations zSetOperations; @Resource private RedisService redisService; @Test public void testObj() throws Exception{ UserVo userVo = new UserVo(); userVo.setAddress("上海"); userVo.setName("测试dfas"); userVo.setAge(123); ValueOperations operations = redisTemplate.opsForValue(); redisService.expireKey("name",20, TimeUnit.SECONDS); String key = RedisKeyUtil.getKey(UserVo.Table,"name",userVo.getName()); UserVo vo = (UserVo) operations.get(key); System.out.println(vo); } @Test public void testValueOption( )throws Exception{ UserVo userVo = new UserVo(); userVo.setAddress("上海"); userVo.setName("jantent"); userVo.setAge(23); valueOperations.set("test",userVo); System.out.println(valueOperations.get("test")); } @Test public void testSetOperation() throws Exception{ UserVo userVo = new UserVo(); userVo.setAddress("北京"); userVo.setName("jantent"); userVo.setAge(23); UserVo auserVo = new UserVo(); auserVo.setAddress("n柜昂周"); auserVo.setName("antent"); auserVo.setAge(23); setOperations.add("user:test",userVo,auserVo); Set result = setOperations.members("user:test"); System.out.println(result); } @Test public void HashOperations() throws Exception{ UserVo userVo = new UserVo(); userVo.setAddress("北京"); userVo.setName("jantent"); userVo.setAge(23); hashOperations.put("hash:user",userVo.hashCode()+"",userVo); System.out.println(hashOperations.get("hash:user",userVo.hashCode()+"")); } @Test public void ListOperations() throws Exception{ UserVo userVo = new UserVo(); userVo.setAddress("北京"); userVo.setName("jantent"); userVo.setAge(23); // listOperations.leftPush("list:user",userVo); // System.out.println(listOperations.leftPop("list:user")); // pop之后 值会消失 System.out.println(listOperations.leftPop("list:user")); } } ``` ### 注解缓存的使用 - @Cacheable:在方法执行前Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;没有则调用方法并将方法返回值放进缓存。 - @CachePut:将方法的返回值放到缓存中。 - @CacheEvict:删除缓存中的数据。 参考: [Redis 学习(二) —— 数据类型及操作](https://www.cnblogs.com/chiangchou/p/7748009.html) [安装参考](http://www.cnblogs.com/chiangchou/p/redis-1.html)