使用 JPA (TreeView) 在同一个 table 中无限分层父子关系
Unlimited Hierarchical child parent relationship in the same table using 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: [
]
}
]
}
]
}
]
}
如果我们设计,我们可以在单个数据库 table(实体,在 spring 数据 jpa 中)构造这种递归树状结构,并通过两次数据库调用将整个树提取到叶节点我们的模型仔细。
开始之前table设计/实体建模让我们总结一些事实
每个类别如果是子类别就应该有一个父类别,否则该类别就是根类别
每个作为子类别的类别都必须有一个根类别。
Category.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
和name
的POJO(不是实体)并将父类别 id
& name
复制到该 POJO 并将其设置为相应的子类别。
我正在尝试创建一个“类别和子类别”实体,我尝试进行一些研究,但找不到好的解决方案,我需要了解如何为该实体建模并获得以下结果!能够以这种树视图格式检索数据。
{
"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: [
]
}
]
}
]
}
]
}
如果我们设计,我们可以在单个数据库 table(实体,在 spring 数据 jpa 中)构造这种递归树状结构,并通过两次数据库调用将整个树提取到叶节点我们的模型仔细。
开始之前table设计/实体建模让我们总结一些事实
每个类别如果是子类别就应该有一个父类别,否则该类别就是根类别
每个作为子类别的类别都必须有一个根类别。
Category.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
和name
的POJO(不是实体)并将父类别 id
& name
复制到该 POJO 并将其设置为相应的子类别。