【PostgresSQL系列】 ltree简介及基于SpringBoot实现 ltree数据增删改查

本文将对PostgresSQL中的ltree进行相关概念介绍,并以示例代码讲解ltree数据增删改查功能的实现。

作者:后端小肥肠

目录

 1.前言

2. 基础概念

2.1. ltree

2.2. lquery

2.3. ltxtquery

2.4. ltree函数及操作符

2.4.1. ltree函数

2.4.2. ltree操作符 

3.开发环境搭建

3.1. 所用工具版本

 3.2. pom依赖

4. 代码讲解

4.1. 目录树查询

4.2. 新增目录树节点

4.3. 更新目录树节点

4.4. 删除目录树节点

5. 结语

6. 参考链接


 1.前言

        在数据库设计和数据管理领域,有效地处理层次结构数据是一项关键任务。PostgreSQL 提供了 ltree 扩展模块,为处理这类数据提供了强大的工具。在本文中,我们将深入探讨 ltree 的相关概念和用法。

2. 基础概念

2.1. ltree

        在 PostgreSQL 中,ltree 是一种专门用于表示层次结构数据的数据类型。每个 ltree 值都可以被看作是一个节点标签序列,这些标签通过点号分隔,形成路径。例如,'A.B.C' 表示一个具有三个节点的树状结构,其中每个节点的标签分别是 A、B 和 C。

       在创建表时,可以使用 ltree 数据类型定义列,从而存储具有层次结构的数据。下面是一个简单的示例:

1. 创建ltree扩展


CREATE EXTENSION ltree;

2. 创建ltree序列

CREATE TABLE my_table (id serial PRIMARY KEY,path ltree
);

3. 插入数据

INSERT INTO my_table (path) VALUES('A.B.C'),('A.D.E'),('X.Y.Z');-- 查询以 A 开头的路径
SELECT * FROM my_table WHERE path ~ 'A.*';

2.2. lquery

        lquery表示一个用于匹配ltree值的类正则表达式的模式。一个简单词匹配一个路径中的那个标签。它允许我们指定路径的模式,以便查找符合条件的数据。通配符和操作符可以用于创建灵活的匹配规则。

以下为使用示例:

-- 查询以 A 开头,以 C 结尾的路径
SELECT * FROM my_table WHERE path ~ 'A.*.C';

2.3. ltxtquery

        ltxtquery表示一种用于匹配ltree值的类全文搜索的模式。它引入了逻辑运算符,如 AND、OR、NOT,以及括号,以支持更复杂的查询操作。

以下为使用示例:

-- 查询路径中同时包含 A 和 B,或者包含 C 的记录
SELECT * FROM my_table WHERE path @ 'A & B | C';

        注意:ltxtquery允许符号之间的空白,但是ltreelquery不允许。一个ltxtquery值包含词,也可能在末尾带有修饰符@、*、%,修饰符具有和lquery中相同的含义。词可以用&(AND)、|(OR)、!(NOT)以及圆括号组合。

2.4. ltree函数及操作符

2.4.1. ltree函数
函数返回类型描述例子结果
subltree(ltree, int start, int end)ltreeltree的从位置start到位置end-1(从 0 开始计)的子路径subltree('Top.Child1.Child2',1,2)Child1
subpath(ltree, int offset, int len)ltreeltree从位置offset开始长度为len的子路径。如果offset为负,则子路径开始于距离路径尾部那么远的位置。如果len为负,则从路径的尾部开始丢掉那么多个标签。subpath('Top.Child1.Child2',0,2)Top.Child1
subpath(ltree, int offset)ltreeltree从位置offset开始一直延伸到路径末尾的子路径。如果offset为负,则子路径开始于距离路径尾部那么远的位置。subpath('Top.Child1.Child2',1)Child1.Child2
nlevel(ltree)integer路径中标签的数量nlevel('Top.Child1.Child2')3
index(ltree a, ltree b)integera中第一次出现b的位置,如果没有找到则为 -1index('0.1.2.3.5.4.5.6.8.5.6.8','5.6')6
index(ltree a, ltree b, int offset)integera中第一次出现b的位置,搜索从offset开始。负的offset表示从距路径尾部-offset个标签的位置开始index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-4)9
text2ltree(text)ltree把text转换成ltree
ltree2text(ltree)text把ltree转换成text
lca(ltree, ltree, ...)ltree最低公共祖先,即最长的公共路径前缀(最多支持 8 个参数)lca('1.2.2.3','1.2.3.4.5.6')1.2
lca(ltree[])ltree最低公共祖先,即最长的公共路径前缀lca(array['1.2.2.3'::ltree,'1.2.3'])1.2
2.4.2. ltree操作符 
操作符返回值描述
ltree @> ltreeboolean左参数是不是右参数的一个祖先(或者相等)?
ltree <@ ltreeboolean左参数是不是右参数的一个后代(或者相等)?
ltree ~ lquerybooleanltree匹配lquery吗?
lquery ~ ltreebooleanltree匹配lquery吗?
ltree ? lquery[]booleanltree匹配数组中的任意lquery吗?
lquery[] ? ltreebooleanltree匹配数组中的任意lquery吗?
ltree @ ltxtquerybooleanltree匹配ltxtquery吗?
ltxtquery @ ltreebooleanltree匹配ltxtquery吗?
ltree || ltreeltree串接ltree路径
ltree || textltree把文本转换成ltree并且串接
text || ltreeltree把文本转换成ltree并且串接
ltree[] @> ltreeboolean数组是否包含ltree的一个祖先?
ltree <@ ltree[]boolean数组是否包含ltree的一个祖先?
ltree[] <@ ltreeboolean数组是否包含ltree的一个后代?
ltree @> ltree[]boolean数组是否包含ltree的一个后代?
ltree[] ~ lqueryboolean数组是否包含匹配lquery的路径?
lquery ~ ltree[]boolean数组是否包含匹配lquery的路径?
ltree[] ? lquery[]booleanltree数组是否包含匹配任意lquery的路径?
lquery[] ? ltree[]booleanltree数组是否包含匹配任意lquery的路径?
ltree[] @ ltxtqueryboolean数组是否包含匹配ltxtquery的路径?
ltxtquery @ ltree[]boolean数组是否包含匹配ltxtquery的路径?
ltree[] ?@> ltreeltree是ltree祖先的第一个数组项;如果没有则是 NULL
ltree[] ?<@ ltreeltree是ltree祖先的第一个数组项;如果没有则是 NULL
ltree[] ?~ lqueryltree匹配lquery的第一个数组项;如果没有则是 NULL
ltree[] ?@ ltxtqueryltree匹配lquery的第一个数组项;如果没有则是 NULL

操作符<@、@>、 @以及~有类似的、 ^<@、^@>、^@、 ^~,只是它们不适用索引。它们只对测试目的有用。

3.开发环境搭建

3.1. 所用工具版本

依赖版本
Spring Boot2.6.3
java1.8
postgres13.12

 3.2. pom依赖

 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId></dependency><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.25</version></dependency></dependencies>

4. 代码讲解

        本文代码为基于ltree构建目录树,包含目录树查询,新增目录节点,修改目录节点,删除目录节点。

4.1. 目录树查询

1. controller层

  @GetMapping("/search")public LtreeCatalogDTO getCatalogList() {return ltreeCatalogService.getCatalogList();}

2. service方法

    public LtreeCatalogDTO getCatalogList() {//设置原始根节点(需要写死一个原始根节点,我在这里直接写死了,可以通过yml配置文件动态指定)LtreeCatalogDTO resTree=new LtreeCatalogDTO("1",null,"目录树",null);List<LtreeCatalogDTO>resChildren=new ArrayList<>();List<LtreeCatalogDTO>childrenList=new ArrayList<>();List<LtreeCatalogDTO>rootList=new ArrayList<>();LambdaQueryWrapper<LtreeCatalog> queryWrapper=new LambdaQueryWrapper<>();List<LtreeCatalog> ltreeCatalogs = baseMapper.selectList(queryWrapper.orderByDesc(LtreeCatalog::getCreateTime));for (LtreeCatalog ltreeCatalog : ltreeCatalogs) {if(("1").equals(ltreeCatalog.getId())){continue;}LtreeCatalog parentCatalog= baseMapper.getParentByChildId(ltreeCatalog.getId());if("1".equals(parentCatalog.getId())){rootList.add(new LtreeCatalogDTO(ltreeCatalog.getId(),parentCatalog.getId(),ltreeCatalog.getName(),null));}else {childrenList.add(new LtreeCatalogDTO(ltreeCatalog.getId(),parentCatalog.getId(),ltreeCatalog.getName(),null));}}for (LtreeCatalogDTO rootNode : rootList) {LtreeCatalogDTO tree=buildTree(childrenList,rootNode);resChildren.add(tree);}resTree.setChildren(resChildren);return resTree;}public  LtreeCatalogDTO buildTree(List<LtreeCatalogDTO> ltreeCatalogDTOS, LtreeCatalogDTO catalogP) {List<LtreeCatalogDTO> childrenList = new ArrayList<>();for (LtreeCatalogDTO catalogC : ltreeCatalogDTOS) {// 当前数据的 parentId 等于 父节点的 id,则该数据是当前父级节点的子级。if (catalogC!=null && catalogC.getParentId().equals(catalogP.getId())) {// 递归调用childrenList.add(buildTree(ltreeCatalogDTOS, catalogC));}}catalogP.setChildren(childrenList);return catalogP;}

上述代码很简单,就是根据数据表中的path(ltree)递归构造目录树,返回给前端。

4.2. 新增目录树节点

1.controller层

    @PostMapping("")public String addCatalog(@RequestBody LtreeCatalog ltreeCatalog) {return ltreeCatalogService.addCatalog(ltreeCatalog);}

2. service方法

    public String addCatalog(LtreeCatalog ltreeCatalog) {return baseMapper.insert(ltreeCatalog)==1?"新增成功":"新增失败";}

ps:在新增数据时会报错 字段 "path" 的类型为 ltree, 但表达式的类型为 character varying 建议:你需要重写或转换表达式,解决的方法新增一个LTreeTypeHandler类,配置到表字段中即可。

LTreeTypeHandler类:

@MappedTypes(String.class)
public class LTreeTypeHandler extends BaseTypeHandler<String> {@Overridepublic void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {PGobject pgObject = new PGobject();pgObject.setType("ltree");pgObject.setValue(s);preparedStatement.setObject(i, pgObject);}@Overridepublic String getNullableResult(ResultSet resultSet, String s) throws SQLException {return resultSet.getString(s);}@Overridepublic String getNullableResult(ResultSet resultSet, int i) throws SQLException {return resultSet.getString(i);}@Overridepublic String getNullableResult(CallableStatement callableStatement, int i) throws SQLException {return callableStatement.getString(i);}
}

配置到实体类表字段中:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class LtreeCatalog implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.ASSIGN_ID)private String id;private String name;@TableField(value = "path", typeHandler = LTreeTypeHandler.class)private String path;private Boolean isDeleted;@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")@TableField(fill = FieldFill.INSERT)private Date createTime;@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;@Versionprivate Integer version;}

发送请求:

4.3. 更新目录树节点

1.controller层

    @PutMapping("")public String updateCatalog(@RequestBody LtreeCatalog ltreeCatalog) throws Exception {return ltreeCatalogService.updateCatalog(ltreeCatalog);}

2. service方法

    @Overridepublic String updateCatalog(LtreeCatalog ltreeCatalog) throws Exception {LtreeCatalog ltreeCatalogExist = baseMapper.selectById(ltreeCatalog.getId());if(ltreeCatalogExist==null){throw new Exception("节点不存在");}return baseMapper.updateById(ltreeCatalog)==1?"新增成功":"新增失败";}

发送请求:

4.4. 删除目录树节点

1.controller层

    @DeleteMapping("/{id}")public void delCatalog(@PathVariable("id") String id) throws Exception {ltreeCatalogService.delCatalog(id);}

2. service方法

目录树删除节点时需要连带删除子节点,那么我们就可以使用ltree操作符中的<@来实现:

    @Overridepublic void delCatalog(String id) throws Exception {LtreeCatalog ltreeCatalog = baseMapper.selectById(id);if(ltreeCatalog==null){throw new Exception("节点不存在");}baseMapper.delCatalog(ltreeCatalog.getPath());}

  baseMapper.delCatalog方法:

    @Delete("select * FROM ltree_catalog WHERE path <@ #{path}::ltree;")void delCatalog(String path);

发送请求:

发送请求前先确定要删除的数据:

 如上图所示我想删除目录b及其下属子节点,那么我们需要把目录b这条数据的id传递给后台:

回看数据表发现目录树b及其子节点已经被删除:

5. 结语

        在本文中,我们探讨了 PostgreSQL 中 ltree 扩展的基本概念和使用方法。通过使用 ltree,我们可以更有效地处理和查询层次结构数据,使得数据库设计和数据管理变得更为灵活和强大。在实际应用中,ltree 提供了一种有力的工具,可以应对各种层次结构的数据建模需求。

6. 参考链接

ltree - PostgreSQL9.6 中文手册

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/447614.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pymysql将爬取到的信息存储到数据库中

爬取平台为电影天堂 获取到的数据仅为测试学习而用 爬取内容为电影名和电影的下载地址 创建表时需要建立三个字段即可 import urllib.request import re import pymysqldef film_exists(film_name, film_link):"""判断插入的数据是否已经存在""&qu…

Linux 路由配置与使用

概念 路由信息用于指导数据包从源地址查找到目的地址传输路径的信息&#xff1b; 路由分类 根据路由信息的来源分为静态路由和动态路由 静态路由 由管理员手动配置的路由表项信息&#xff0c;根据路由形式的不同&#xff0c;静态路由又可细分为&#xff1a; 直连路由&#xf…

Vue3+TS+Vite+Pinia学习总结

VUE3介绍 vue2和vue3之间的区别 因为需要遍历data对象上所有属性&#xff0c;所以如果data对象属性结构嵌套很深&#xff0c;就会存在性能问题。因为需要遍历属性&#xff0c;所有需要提前知道对象上有哪些属性&#xff0c;才能将其转化为getter和setter,所以vue2中无法将data新…

Linux部署lomp环境,安装typecho、WordPress博客

部署lomp环境&#xff0c;安装typecho、WordPress博客 一、环境要求1.1.版本信息1.2.准备阿里云服务器【新用户免费使用三个月】1.3.准备远程工具【FinalShell】 二、Linux下安装openresty三、Linux下安装Mysql四、安装Apache【此步骤可省略】4.1.安装Apache服务及其扩展包4.2.…

Git―基本操作

Git ⛅认识 Git⛅安装 GitCentos(7.6)Ubuntu ⛅Git―基本操作创建本地仓库&#x1f342;配置本地仓库&#x1f342;工作区, 暂存区, 版本库&#x1f342;版本库工作区 添加文件&#x1f342;查看文件&#x1f342;修改文件&#x1f342;版本回退&#x1f342;☃️案例 撤销修改…

2024年美赛 (D题ICM)| 湖流网络水位控制 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 让我们来看看美赛的D题&#xff01; 完整内容可以在文章末尾领…

SpringBoot security 安全认证(二)——登录拦截器

本节内容&#xff1a;实现登录拦截器&#xff0c;除了登录接口之外所有接口访问都要携带Token&#xff0c;并且对Token合法性进行验证&#xff0c;实现登录状态的保持。 核心内容&#xff1a; 1、要实现登录拦截器&#xff0c;从Request请求中获取token&#xff0c;从缓存中获…

STM32 1-5

目录 STM32简介 点亮PC13LED GPIO LED闪烁 LED流水灯 按键控制LED 光敏传感器控制蜂鸣器 OLED调试工具 OLED显示 EXTI外部中断 对射式红外传感器计次 旋转编码器计次 继续 STM32简介 点亮PC13LED main.c #include "stm32f10x.h" // D…

【前端模板】bootstrap5实现蓝色数码网站BigTech(电商适用,附带源码)

一、需求分析 数码电商网站是指专门销售数码产品的在线商城。它们提供了一个平台&#xff0c;供消费者浏览、选择和购买各种数码产品&#xff0c;如智能手机、电脑、相机、家电等。这些网站通常提供以下功能&#xff1a; 产品展示&#xff1a;数码电商网站展示各种数码产品的详…

从零开始:构建高效的 JMeter 集群压测环境

当面对大量用户模拟和性能测量需求时&#xff0c;单台计算机运行 JMeter 往往显得力不从心。因此&#xff0c;构建一个多节点的JMeter集群成为了一种提升测试性能的有效途径。接下来&#xff0c;本文将详细介绍如何组建和配置一个JMeter测试集群。 一、准备工作&#xff1a;服…

英伟达盯上了华为!特供版AI芯片价格首次曝光!起售价11万元低于华为

大家好我是二狗。 据《路透社》报道&#xff0c;英伟达最近几周已开始接受经销商对中国特供版AI芯片H20 的预订&#xff0c;英伟达给出每块显卡 12000 美元&#xff08;8.6 万元人民币&#xff09;至 15000 美元&#xff08;10.7 万元人民币&#xff09;之间的定价。 一些经销…

Django响应式图像库django-pictures

什么是响应式图像&#xff1f; 响应式设计是指网页在不同尺寸的设备上都有良好的显示效果。响应式设计的网页图像&#xff0c;就是响应式图像。 django-pictures是使用现代代码&#xff08;如 AVIF 和 WebP&#xff09;的响应式跨浏览器图像库。 特点 使用 Picture 标签的响应…