redis存储树结构数据
本文主要讲解两方面内容:1.redis如何存储树结构数据。2.java操作redis时选取哪种序列化器。
1. redis如何存储树结构数据
先抛出结论,树结构数据在redis中的存储形式如下:
1.1 前置条件
- spring-boot-starter-data-redis(2.1.8)
- fastjson(1.2.61)
- redis可视化工具 Redis Desktop Manager
1.2 树结构数据在redis中的存储形式(数据结构)
假设有如上典型的组织机构数据,前端需要按层级展示,分层级查询指定节点的子集。
1.2.1 redis中Key的设计
redisKey={NAME_SPACE}:{level}:{parentId}
NAME_SPACE:该数据所在命名空间
level:当前层级
parentId:父节点id,可为空
1.2.2 查询
- 按层级查询:输入指定层级,返回该层级下的所有节点列表
入参:
{ "level":3 }
返回:
[ { "nodeId": "010101", "nodeName": "人事部" }, { "nodeId": "010102", "nodeName": "技术部" }, { "nodeId": "010301", "nodeName": "人事部" }, { "nodeId": "010202", "nodeName": "技术部" }, { "nodeId": "010201", "nodeName": "人事部" } ]
- 查询指定节点的所有子节点:输入该节点id和当前层级
入参:
{ "nodeId":"0102", "level":2 }
返回:
[ { "nodeId": "010202", "nodeName": "技术部" }, { "nodeId": "010201", "nodeName": "人事部" } ]
1.2.3 后台实现
主要代码:
/** * 查询 指定层级下某个父节点的所有子节点 * * @param level 层级 * @param parentId 父节点id,为空时查询该层级下所有节点 * @return 指定层级下某个父节点的所有子节点 */ @SuppressWarnings("unchecked") public List<OrgBean> getSubTree(int level, @Nullable String parentId) { List<OrgBean> beanList; if (StringUtils.isBlank(parentId) && level != 1) { //parentId 为空,模糊匹配查询 beanList = patternSearch(level); } else { //parentId 不为空,精确匹配查询 beanList = exactlySearch(level, parentId); } return beanList; } @SuppressWarnings("unchecked") private List<OrgBean> patternSearch(int level) { //SCAN 0 MATCH {NAME_SPACE}:{level}:* COUNT 10000 String pattern = getRedisKey("*", level); logger.info("redisKey:{}", pattern); List<String> keys = (List<String>) redisTemplate.execute(connection -> { List<String> keyStrList = new ArrayList<>(); RedisKeyCommands keysCmd = connection.keyCommands(); //采用 SCAN 命令,迭代遍历所有key Cursor<byte[]> cursor = keysCmd.scan(ScanOptions.scanOptions().match(pattern).count(10000L).build()); while (cursor.hasNext()) { keyStrList.add(new String(cursor.next(), StandardCharsets.UTF_8)); } return keyStrList; }, true); if (isNotEmpty(keys)) { return keys.stream().flatMap(key -> { List list = listOperations.range(key, 0, -1); return deserializeJsonList(list, OrgBean.class).stream(); }).collect(toList()); } else { return Collections.emptyList(); } } @SuppressWarnings("unchecked") private List<OrgBean> exactlySearch(int level, String parentId) { List<OrgBean> beanList = new ArrayList<>(); String redisKey = getRedisKey(parentId, level); logger.info("redisKey:{}", redisKey); Boolean hasKey = redisTemplate.hasKey(redisKey); if (Boolean.valueOf(true).equals(hasKey)) { List jsonList = listOperations.range(redisKey, 0, -1); beanList = deserializeJsonList(jsonList, OrgBean.class); } return beanList; } private <T> List<T> deserializeJsonList(List jsonList, Class<T> clazz) { ArrayList<T> beanList = new ArrayList<>(); if (isNotEmpty(jsonList)) { //反序列化为指定类型的bean for (Object o : jsonList) { if (nonNull(o)) { T bean = JSON.toJavaObject((JSONObject) o, clazz); beanList.add(bean); } } } return beanList; } /** * redisKey: {NAME_SPACE}:{level}:{parentId} */ private String getRedisKey(String parentId, int level) { return concat(DELIMIT, NAME_SPACE, level, parentId); } /** * 连接字符串通用方法 * * @param delimit 分隔符 * @param element 字符串元素 * @return 按指定分隔符连接的字符串 */ private String concat(String delimit, Object... element) { if (isNull(element) || element.length == 0) { return null; } return Stream.of(element).filter(Objects::nonNull) .map(String::valueOf).filter(StringUtils::isNoneBlank).collect(joining(delimit)); }
2 java操作redis,设置序列化器
org.springframework.data.redis.serializer.JdkSerializationRedisSerializer
org.springframework.data.redis.serializer.StringRedisSerializer
org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
com.alibaba.fastjson.support.spring.FastJsonRedisSerializer
com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer
spring-boot-starter-data-redis 默认采用 JdkSerializationRedisSerializer,这样序列化后的数据无法通过工具直观地展示,通常redis的Key采用StringRedisSerializer,value采用JSON格式化后存储。
json序列化器分别有两种:JsonRedisSerializer和GenericJsonRedisSerializer,Jackson和FastJson分别有对应的实现。
2.1 两种序列化器的区别
1)JsonRedisSerializer 序列化器,针对指定类型的javaBean,初始化的时候需指定泛型。反序列化时,需要做类型转换。
2)GenericJsonRedisSerializer 序列化器,无需指定特定类型,redis服务器中将存储具体数据的类型信息。反序列化时,直接得到既定类型,不需要做类型转换。
举例说明:
2.1.1 *JsonRedisSerializer 序列化器,构造方法:
com.alibaba.fastjson.support.spring.FastJsonRedisSerializer public FastJsonRedisSerializer(Class<T> type) { this.type = type; }
org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer /** * Creates a new {@link Jackson2JsonRedisSerializer} for the given target {@link Class}. * * @param type */ public Jackson2JsonRedisSerializer(Class<T> type) { this.javaType = getJavaType(type); }
2.1.2 *JsonRedisSerializer 序列化器,redis服务器中存储的数据格式:
2.1.3 Generic*JsonRedisSerializer 序列化器,构造方法:
com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer public class GenericFastJsonRedisSerializer implements RedisSerializer<Object> {}
org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Object> { /** * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing. */ public GenericJackson2JsonRedisSerializer() { this((String) null); } }
2.1.4 Generic*JsonRedisSerializer 序列化器,redis服务器中存储的数据格式:
2.1.5 *JsonRedisSerializer 序列化器,反序列化后的类型
public void testDeserialize() { Object val1 = fastJsonTemp.opsForValue().get("emp1_fastJson"); Object val2 = genericFastJsonTemp.opsForValue().get("emp1_genericFastJson"); Object val3 = jacksonTemp.opsForValue().get("emp1_jackson"); Object val4 = genericJacksonTemp.opsForValue().get("emp1_genericJackson"); //class com.alibaba.fastjson.JSONObject log.info(val1.getClass().toString()); //class com.fcg.redis.orgtree.Employee log.info(val2.getClass().toString()); //class java.util.LinkedHashMap log.info(val3.getClass().toString()); //class com.fcg.redis.orgtree.Employee log.info(val4.getClass().toString()); }
2.2 选用FastJsonRedisSerializer
理由:
- 项目中统一采用FastJson,作为JSON序列化工具,因其效率较高。
- Redis中仅需存储数据,不宜限定该数据的具体JavaBean类型,利于不同项目间共享缓存数据。
缺点:
- 由于原始数据缺少类型信息,取出数据后如果需要按照既定类型操作(setters/getters),要多一步转换操作,如:
{ //取出数据类型为 JSONObject List jsonList = listOperations.range(redisKey, 0, -1); //反序列化为指定类型 beanList = deserializeJsonList(jsonList, OrgBean.class); } private <T> List<T> deserializeJsonList(List jsonList, Class<T> clazz) { ArrayList<T> beanList = new ArrayList<>(); if (isNotEmpty(jsonList)) { //反序列化为指定类型的bean for (Object o : jsonList) { if (nonNull(o)) { T bean = JSON.toJavaObject((JSONObject) o, clazz); beanList.add(bean); } } } return beanList; }
2.3 RedisTemplate 配置
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { //泛型用Object,可序列化所有类型 FastJsonRedisSerializer<Object> jsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setKeySerializer(stringRedisSerializer); template.setValueSerializer(jsonRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setHashValueSerializer(stringRedisSerializer); template.setDefaultSerializer(jsonRedisSerializer); template.setConnectionFactory(connectionFactory); template.afterPropertiesSet(); return template; }
在使用RedisTemplate时,不要指定泛型,可序列化所有类型,否则在使用ListOperations 时,会把整个List作为一个元素:
public class OrgService { @Resource(name = "redisTemplate") private RedisTemplate redisTemplate; @Resource(name = "redisTemplate") private ListOperations listOperations; }
源码地址
https://gitee.com/thanksm/redis_learn/tree/master/orgtree
- 点赞 1
- 收藏
- 分享
- 文章举报
- redis之存储---数据结构
- Redis之(二)数据类型及存储结构
- Redis的五种存储数据结构和常用命令
- 使用stringRedisTemplate操作redis hash结构数据只能存储String类型的问题
- redis 存储数据结构
- Redis数据存储结构之String
- redis 存储数据结构及持久化方式--rdb 、aof
- redis中的数据类型及存储结构
- Redis 数据类型分析 字符串 哈希 列表 集合 有序集合 优缺点 分析 注意事项 存储结构
- Redis学习笔记之二:Redis的数据存储结构
- 我知道点redis-数据结构与对象(对象)-对象存储
- Redis(一) 数据结构与底层存储 & 事务 & 持久化 & lua
- Redis之(二)数据类型及存储结构
- Redis之(二)数据类型及存储结构
- 数据结构(七)图--图的基本概念及存储结构
- redis 类型底层存储结构
- Redis 数据存储位置 导出数据
- 用Redis存储大量数据
- redis源码系列-数据结构(adlist/ziplist/dict)
- 数据结构之串(上)——串的基本概念与存储结构