java - 使用 JPA (TreeView) 在同一个表中实现无限的分层子父关系

标签 java sql spring jpa

我正在尝试创建一个“类别和子类别”实体,我尝试做一些研究,但找不到好的解决方案,我需要了解如何对该实体进行建模并获得以下结果!能够检索此 TreeView 格式的数据。

   {
"id": 1,
"name": "Account 1",
"children": [
    {
        "id": 2,
        "name": "Account 1.1",
        "parent": {
            "id": 1,
            "name": "Account 1"
        }
    },
    {
        "id": 3,
        "name": "Account 1.2",
        "parent": {
            "id": 1,
            "name": "Account 1"
        },
        children: [
            {
                "id": 4,
                "name": "Account 1.2.1",
                "children": [
                    {
                        "id": 5,
                        "name": "Account 1.2.1.1",
                        "parent": {
                            "id": 4,
                            "name": "Account 1.2.1"
                        }
                    },
                    {
                        "id": 6,
                        "name": "Account 1.2.1.2",
                        "parent": {
                            "id": 4,
                            "name": "Account 1.2.1"
                        },
                        children: [
                        
                        ]
                    }
                ]
            }
        ]
    }
]

}

最佳答案

如果我们仔细设计我们的模型,我们可以在单个数据库表(实体,在 spring data jpa 中)中构建这种递归树状结构,并通过两个数据库调用将整个树获取到叶节点。

在开始表设计/实体建模之前,让我们总结一些事实

  1. 每个类别如果是子类别,则应该有一个父类别,否则该类别是根类别

  2. 作为子类别的每个类别都必须有一个根类别。

类别.java

@Entity
public class Category {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long categoryId;

    @Column(nullable = false)
    public String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_category_id")
    @JsonIgnore
    public Category parentCategory;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "root_category_id")
    @JsonIgnore
    public Category rootCategory;
    
    @Transient
    public List<Category> childrens = new ArrayList<Category>();
}

CategoryRepository.java

@Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
    
    @Query("SELECT category FROM Category category "
            + " WHERE category.parentCategory.categoryId IS NULL")
    public List<Category> findAllRoots();

    @Query("SELECT category FROM Category category"
            + " WHERE category.rootCategory.categoryId IN :rootIds ")
    public List<Category> findAllSubCategoriesInRoot(@Param("rootIds") List<Long> rootIds);
}

CategoryController.java

@RestController
public class CategoryController {
    
    @Autowired
    public CategoryRepository categoryRepository;

    @GetMapping("/categories")
    @Transactional(readOnly = true)
    public List<Category> getCategories() {
        List<Category> rootCategories = categoryRepository.findAllRoots(); // first db call

        // Now Find all the subcategories
        List<Long> rootCategoryIds = rootCategories.stream().map(Category::getCategoryId).collect(Collectors.toList());
        List<Category> subCategories = categoryRepository.findAllSubCategoriesInRoot(rootCategoryIds); // second db call

        subCategories.forEach(subCategory -> {
            subCategory.getParentCategory().getChildrens().add(subCategory); // no further db call, because everyone inside the root is in the persistence context.
        });

        return rootCategories;
    }
}

示例数据集

-- root
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (1, 'A', null, null);

-- first level
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (2, 'B', 1, 1);
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (3, 'C', 1, 1);

-- second level
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (4, 'D', 2, 1);
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (5, 'E', 3, 1);
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (6, 'F', 3, 1);

-- another root
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (7, 'P', null, null);

-- first level of another root
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (8, 'Q', 7, 7);
INSERT INTO category (category_id, name, parent_category_id, root_category_id) VALUES (9, 'R', 7, 7);

生成的响应

[
    {
        "categoryId": 1,
        "name": "A",
        "childrens": [
            {
                "categoryId": 2,
                "name": "B",
                "childrens": [
                    {
                        "categoryId": 4,
                        "name": "D",
                        "childrens": []
                    }
                ]
            },
            {
                "categoryId": 3,
                "name": "C",
                "childrens": [
                    {
                        "categoryId": 5,
                        "name": "E",
                        "childrens": []
                    },
                    {
                        "categoryId": 6,
                        "name": "F",
                        "childrens": []
                    }
                ]
            }
        ]
    },
    {
        "categoryId": 7,
        "name": "P",
        "childrens": [
            {
                "categoryId": 8,
                "name": "Q",
                "childrens": []
            },
            {
                "categoryId": 9,
                "name": "R",
                "childrens": []
            }
        ]
    }
]

我故意跳过了parent根据您的示例响应,因为在正文中添加父级会不必要地增加响应大小。

如果你真的需要parent键入所有子类别,然后您必须引入另一个包含id的POJO(不是实体) & name父类别并复制父类别 id & name进入该 POJO 并将其设置为相应的子类别。

关于java - 使用 JPA (TreeView) 在同一个表中实现无限的分层子父关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72464777/

相关文章:

java - hibernate 将 Set<Enum> 存储到数据库中

java - Spring-JPA 和 Spring-Data-JPA 之间的区别

java - 考虑到不添加周末,如何将营业时间添加到日期? - java

mysql - 输出中只有一个日期列的 SQL 子查询

sql - MySQL:根据关联的ID限制输出

sql - 获取 SQL 字符串中每个单词的第一个字母

java - 将 Grizzly2.2.X 与 Jersey 和 Spring 集成

java - 此处不允许使用数组初始值设定项

java - 如何检查时间戳(纪元时间)是今天还是昨天[android]

java - 从ObjectName获取Google云存储文件