Jimmer VS MyBatisPlus查询自关联表

news/2024/11/9 0:32:43/

首发于Enaium的个人博客


本文是对Jimmer文档中对象抓取器-自关联递归抓取部分的介绍,之后会对比MyBatisPlus的查询自关联表的能力。

对象抓取器是 jimmer-sql 一个非常强大的特征,具备可媲美 GraphQL 的能力。
即使用户不采用任何 GraphQL 相关的技术栈,也能在 SQL 查询层面得到和 GraphQL 相似的对象图查询能力。

准备数据库和实体类

create table tree_node(node_id bigint not null,name varchar(20) not null,parent_id bigint
);
alter table tree_nodeadd constraint pk_tree_nodeprimary key(node_id);
alter table tree_nodeadd constraint uq_tree_nodeunique(parent_id, name);
alter table tree_nodeadd constraint fk_tree_node__parentforeign key(parent_id)references tree_node(node_id);insert into tree_node(node_id, name, parent_id
) values(1, 'Home', null),(2, 'Food', 1),(3, 'Drinks', 2),(4, 'Coca Cola', 3),(5, 'Fanta', 3),(6, 'Bread', 2),(7, 'Baguette', 6),(8, 'Ciabatta', 6),(9, 'Clothing', 1),(10, 'Woman', 9),(11, 'Casual wear', 10),(12, 'Dress', 11),(13, 'Miniskirt', 11),(14, 'Jeans', 11),(15, 'Formal wear', 10),(16, 'Suit', 15),(17, 'Shirt', 15),(18, 'Man', 9),(19, 'Casual wear', 18),(20, 'Jacket', 19),(21, 'Jeans', 19),(22, 'Formal wear', 18),(23, 'Suit', 22),(24, 'Shirt', 22)
;
@Entity
public interface TreeNode {@Id@Column(name = "NODE_ID")long id();String name();@Null@ManyToOneTreeNode parent();@OneToMany(mappedBy = "parent")List<TreeNode> childNodes();
}

指定查询的深度

我们可以看到,这是一个自关联的表,每个节点都有一个父节点,也可以有多个子节点。

使用 Jimmer 的Fetcher功能,我们可以很容易的查询出这个表的所有节点,并且可以很容易的控制查询的深度,还有条件查询。

TreeNodeTable node = TreeNodeTable.$;List<TreeNode> treeNodes = sqlClient.createQuery(node)//创建一个查询.where(node.parent().isNull())//查询条件,这里查询出所有的根节点,也就是parent_id为null的节点.select(//查询的字段node.fetch(TreeNodeFetcher.$.name()//查询节点的名称.childNodes(TreeNodeFetcher.$.name(),//查询子节点的名称it -> it.depth(2)//查询子节点的深度,这里查询2层))).execute();

如果你使用Kotlin,那么你可以这样写

val treeNodes = sqlClient.createQuery(TreeNode::class) {where(table.parent.isNull())//查询条件,这里查询出所有的根节点,也就是parent_id为null的节点select(table.fetchBy {allScalarFields()//查询节点的所有字段childNodes({depth(2)//查询子节点的深度,这里查询2层}) {allScalarFields()//查询子节点的所有字段}})}.execute()

生成的 SQL 语句

第 0 层

selecttb_1_.NODE_ID,tb_1_.NAME
from TREE_NODE as tb_1_
wheretb_1_.PARENT_ID is null

第 1 层

selecttb_1_.PARENT_ID,tb_1_.NODE_ID,tb_1_.NAMEfrom TREE_NODE as tb_1_
wheretb_1_.PARENT_ID in (?)

第 2 层

selecttb_1_.PARENT_ID,tb_1_.NODE_ID,tb_1_.NAME
from TREE_NODE as tb_1_
wheretb_1_.PARENT_ID in (?, ?)

查询的结果

{"id": 1,"name": "Home","childNodes": [{"id": 9,"name": "Clothing","childNodes": [{ "id": 18, "name": "Man" },{ "id": 10, "name": "Woman" }]},{"id": 2,"name": "Food","childNodes": [{ "id": 6, "name": "Bread" },{ "id": 3, "name": "Drinks" }]}]
}

查询无限层级的树

如果你想查询无限层级的树,那么你可以这样写

TreeNodeTable node = TreeNodeTable.$;List<TreeNode> treeNodes = sqlClient.createQuery(node).where(node.parent().isNull()).select(node.fetch(TreeNodeFetcher.$.name().childNodes(TreeNodeFetcher.$.name(),it -> it.recursive()//查询无限层级的树,这里不需要指定深度,也就是把depth()方法去掉换成recursive()方法))).execute();
val treeNodes = sqlClient.createQuery(TreeNode::class) {where(table.parent.isNull())select(table.fetchBy {allScalarFields()childNodes({recursive()}) {allScalarFields()}})}.execute()

生成的 SQL 语句

第 0 层

selecttb_1_.NODE_ID,tb_1_.NAME
from TREE_NODE as tb_1_
wheretb_1_.PARENT_ID is null

第 1 层

selecttb_1_.PARENT_ID,tb_1_.NODE_ID,tb_1_.NAMEfrom TREE_NODE as tb_1_
wheretb_1_.PARENT_ID in (?)

第 2 层

selecttb_1_.PARENT_ID,tb_1_.NODE_ID,tb_1_.NAME
from TREE_NODE as tb_1_
wheretb_1_.PARENT_ID in (?, ?)

第 3 层

selecttb_1_.PARENT_ID,tb_1_.NODE_ID,tb_1_.NAME
from TREE_NODE as tb_1_
wheretb_1_.PARENT_ID in (?, ?, ?, ?)

第 4 层

selecttb_1_.PARENT_ID,tb_1_.NODE_ID,tb_1_.NAME
from TREE_NODE as tb_1_
wheretb_1_.PARENT_ID in (?, ?, ?, ?, ?, ?, ?, ?)

第 5 层

selecttb_1_.PARENT_ID,tb_1_.NODE_ID,tb_1_.NAME
from TREE_NODE as tb_1_
wheretb_1_.PARENT_ID in (?, ?, ?, ?, ?, ?, ?, ?, ?)

查询结果

{"id": 1,"name": "Home","childNodes": [{"id": 9,"name": "Clothing","childNodes": [{"id": 18,"name": "Man","childNodes": [{"id": 19,"name": "Casual wear","childNodes": [{ "id": 20, "name": "Jacket", "childNodes": [] },{ "id": 21, "name": "Jeans", "childNodes": [] }]},{"id": 22,"name": "Formal wear","childNodes": [{ "id": 24, "name": "Shirt", "childNodes": [] },{ "id": 23, "name": "Suit", "childNodes": [] }]}]},{"id": 10,"name": "Woman","childNodes": [{"id": 11,"name": "Casual wear","childNodes": [{ "id": 12, "name": "Dress", "childNodes": [] },{ "id": 14, "name": "Jeans", "childNodes": [] },{ "id": 13, "name": "Miniskirt", "childNodes": [] }]},{"id": 15,"name": "Formal wear","childNodes": [{ "id": 17, "name": "Shirt", "childNodes": [] },{ "id": 16, "name": "Suit", "childNodes": [] }]}]}]},{"id": 2,"name": "Food","childNodes": [{"id": 6,"name": "Bread","childNodes": [{ "id": 7, "name": "Baguette", "childNodes": [] },{ "id": 8, "name": "Ciabatta", "childNodes": [] }]},{"id": 3,"name": "Drinks","childNodes": [{ "id": 4, "name": "Coca Cola", "childNodes": [] },{ "id": 5, "name": "Fanta", "childNodes": [] }]}]}]
}

每个查询的节点是否递归

如果你想每个查询的节点是否递归,那么你可以这样写

TreeNodeTable node = TreeNodeTable.$;List<TreeNode> treeNodes = sqlClient.createQuery(node).where(node.parent().isNull()).select(node.fetch(TreeNodeFetcher.$.name().childNodes(TreeNodeFetcher.$.name(),it -> it.recursive(args ->!args.getEntity().name().equals("Clothing")//每个查询的节点是否递归,这里可以根据实体的属性来判断是否递归)))).execute();
val treeNodes = sqlClient.createQuery(TreeNode::class) {where(table.parent.isNull())select(table.fetchBy {allScalarFields()childNodes({recursive {entity.name != "Clothing"//每个查询的节点是否递归,这里可以根据实体的属性来判断是否递归}}) {allScalarFields()}})}.execute()

这样就可以实现每个查询的节点是否递归了

使用 MybatisPlus 来查询树形结构

定义实体

@Data
@TableName("tree_node")
public class TreeNode {@TableIdprivate Long nodeId;private String name;@TableField(exist = false)private List<TreeNode> childNodes;
}

定义 Mapper

@Mapper
public interface TreeNodeMapper extends BaseMapper<TreeNode> {
}

查询树形结构的 Service

@Service
@AllArgsConstructor
public class TreeNodeService {private final TreeNodeMapper treeNodeMapper;public List<TreeNode> getTree() {// 查询根节点列表List<TreeNode> rootNodes = selectRoots();// 遍历根节点,递归查询每个节点的子孙节点for (TreeNode rootNode : rootNodes) {this.getChildren(rootNode);}return rootNodes;}private void getChildren(TreeNode node) {// 查询子节点List<TreeNode> children = selectByParentId(node.getNodeId());// 遍历子节点,递归查询子节点的子孙节点for (TreeNode child : children) {this.getChildren(child);}node.setChildNodes(children);}public List<TreeNode> selectRoots() {QueryWrapper<TreeNode> wrapper = new QueryWrapper<>();wrapper.isNull("parent_id");// 查询根节点,parent_id为nullreturn treeNodeMapper.selectList(wrapper);}public List<TreeNode> selectByParentId(Long parentId) {QueryWrapper<TreeNode> wrapper = new QueryWrapper<>();wrapper.eq("parent_id", parentId);// 查询子节点,parent_id为当前节点的idreturn treeNodeMapper.selectList(wrapper);}
}

查询结果

{"nodeId": 1,"name": "Home","childNodes": [{"nodeId": 9,"name": "Clothing","childNodes": [{"nodeId": 18,"name": "Man","childNodes": [{"nodeId": 19,"name": "Casual wear","childNodes": [{ "nodeId": 20, "name": "Jacket", "childNodes": [] },{ "nodeId": 21, "name": "Jeans", "childNodes": [] }]},{"nodeId": 22,"name": "Formal wear","childNodes": [{ "nodeId": 24, "name": "Shirt", "childNodes": [] },{ "nodeId": 23, "name": "Suit", "childNodes": [] }]}]},{"nodeId": 10,"name": "Woman","childNodes": [{"nodeId": 11,"name": "Casual wear","childNodes": [{ "nodeId": 12, "name": "Dress", "childNodes": [] },{ "nodeId": 14, "name": "Jeans", "childNodes": [] },{ "nodeId": 13, "name": "Miniskirt", "childNodes": [] }]},{"nodeId": 15,"name": "Formal wear","childNodes": [{ "nodeId": 17, "name": "Shirt", "childNodes": [] },{ "nodeId": 16, "name": "Suit", "childNodes": [] }]}]}]},{"nodeId": 2,"name": "Food","childNodes": [{"nodeId": 6,"name": "Bread","childNodes": [{ "nodeId": 7, "name": "Baguette", "childNodes": [] },{ "nodeId": 8, "name": "Ciabatta", "childNodes": [] }]},{"nodeId": 3,"name": "Drinks","childNodes": [{ "nodeId": 4, "name": "Coca Cola", "childNodes": [] },{ "nodeId": 5, "name": "Fanta", "childNodes": [] }]}]}]
}

查询树形结构的 Service 并指定查询深度

@Service
@AllArgsConstructor
public class TreeNodeService {private final TreeNodeMapper treeNodeMapper;public List<TreeNode> getTree(int depth) {// 查询根节点列表List<TreeNode> rootNodes = selectRoots();// 遍历根节点,递归查询每个节点的子孙节点for (TreeNode rootNode : rootNodes) {this.getChildren(rootNode, depth, 0);}return rootNodes;}private void getChildren(TreeNode node, int maxDepth, int currentDepth) {if (currentDepth >= maxDepth) {// 当前深度达到最大深度,终止递归并返回结果node.setChildNodes(Collections.emptyList());return;}// 查询子节点List<TreeNode> children = selectByParentId(node.getNodeId());// 遍历子节点,递归查询子节点的子孙节点for (TreeNode child : children) {this.getChildren(child, maxDepth, currentDepth + 1);}node.setChildNodes(children);}public List<TreeNode> selectRoots() {QueryWrapper<TreeNode> wrapper = new QueryWrapper<>();wrapper.isNull("parent_id");return treeNodeMapper.selectList(wrapper);}public List<TreeNode> selectByParentId(Long parentId) {QueryWrapper<TreeNode> wrapper = new QueryWrapper<>();wrapper.eq("parent_id", parentId);return treeNodeMapper.selectList(wrapper);}
}

查询结果

{"nodeId": 1,"name": "Home","childNodes": [{"nodeId": 9,"name": "Clothing","childNodes": [{ "nodeId": 18, "name": "Man", "childNodes": [] },{ "nodeId": 10, "name": "Woman", "childNodes": [] }]},{"nodeId": 2,"name": "Food","childNodes": [{ "nodeId": 6, "name": "Bread", "childNodes": [] },{ "nodeId": 3, "name": "Drinks", "childNodes": [] }]}]
}

查询树形结构的 Service 并指定查询深度和查询条件

不好意思,这个功能我还没想好怎么用 MybatisPlus 实现,所以这里就不写了。

总结

这么一对比,使用MybatisPlus的代码量确实多了不少并且很复杂,又是递归又是递归计数等等,而Jimmer使用了Fetcher就会更容易的查出所有多层节点,并且代码量也非常少


http://www.ppmy.cn/news/109950.html

相关文章

Sql Server 自动备份

Sql Server 自动备份 文章目录 Sql Server 自动备份1. 打开SQL Server&#xff0c;在管理下找到”维护计划”&#xff0c;右键点击”维护计划向导”&#xff0c;如图&#xff1b;2. 再次点击维护计划向导3. 在选择维护任务下勾选”备份数据库”、”清楚维护任务”4.选择需要备份…

项目文件模板-项目章程

项目目的或批准项目的原因 可测量的项目目标和相关的成果标准 高层级需求 高层级项目需求 高层级风险 总体里程碑进度计划 总体预算 干系人清单 项目审批要求&#xff08;什么构成项目成功 谁决定 谁签署&#xff09; 委派的项目经理及其职责 发起人或其他批准项目章程的人员姓…

Seata 分布式事务-应用实例

Seata 分布式事务-应用实例 需求分析/图解 需求&#xff1a;完成下订单功能&#xff0c;由三个微服务模块协同完成, 涉及到多数据库, 多张表 分析 黑色线是执行顺序线 红色线是想Seata Server注册 最后紫色线是决定是否提交和回滚 项目目录 主题包结构都是一样的但是类名字…

eventfd 和 epoll 的结合使用

一.eventfd介绍 eventfd 是 Linux 的一个系统调用&#xff0c;创建一个文件描述符用于事件通知&#xff0c;自 Linux 2.6.22 以后开始支持。 接口及参数介绍 #include <sys/eventfd.h> int eventfd(unsigned int initval, int flags);eventfd() 创建一个 eventfd 对象&…

Redis高级篇 - 多级缓存

多级缓存 1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后&#xff0c;先查询Redis&#xff0c;如果未命中则查询数据库&#xff0c;如图&#xff1a; 存在下面的问题&#xff1a; 请求要经过Tomcat处理&#xff0c;Tomcat的性能成为整个系统的瓶颈 Redis缓存失效时…

【PWN · ret2libc】[2021 鹤城杯]babyof

Linux_64的经典ret2libc题目&#xff0c;有必要好好整理总结一下其中的流程和注意点 目录 前言 一、题目重述 二、exp&#xff08;思考与理解在注释&#xff09; 三、经验总结 攻击步骤: 注意要点 四、疑问 前言 64位Linux和32位Linux确乎有着关于参数传递上的不同&a…

四轴姿态解算-imu算法

理论篇 欧拉角四元数方向余弦矩阵 强调三者描述的是坐标系A,A之间的变换关系 欧拉角&#xff0c;四元数&#xff0c;方向余弦矩阵都可以描述四轴的姿态变换 注意这里强调的是变换 三者转换公式 一阶龙格库塔法 核心要点简介: 假设一阶函数随时间关系如: y a * T1b 则,在经…

flex 布局的基本概念 - 详解

flex 布局的基本概念 Flexible Box 模型&#xff0c;通常被称为 flexbox&#xff0c;是一种一维的布局模型。它给 flexbox 的子元素之间提供了强大的空间分布和对齐能力。本文给出了 flexbox 的主要特性&#xff0c;更多的细节将在别的文档中探索。我们说 flexbox 是一种一维的…