无法使用Java完全递归树层次结构
我们已经创建了一个SpringBootMicroService,它发出一个HTTP GET来从MySQL数据库中提取数据(每个节点),MySQL数据库的数据设置在一个表中 我能够在特定级别获取节点的子节点,但还需要能够查看所有子节点(即使它需要不同的REST调用和服务方法) 我在技术堆栈中使用Java1.8、SpringBoot1.5.6.RELEASE、JPA和MySQL 5 pom.xml:无法使用Java完全递归树层次结构,java,spring-boot,recursion,tree,spring-data-jpa,Java,Spring Boot,Recursion,Tree,Spring Data Jpa,我们已经创建了一个SpringBootMicroService,它发出一个HTTP GET来从MySQL数据库中提取数据(每个节点),MySQL数据库的数据设置在一个表中 我能够在特定级别获取节点的子节点,但还需要能够查看所有子节点(即使它需要不同的REST调用和服务方法) 我在技术堆栈中使用Java1.8、SpringBoot1.5.6.RELEASE、JPA和MySQL 5 pom.xml: <parent> <groupId>org.springframew
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
NodeRepository:
@Repository
public interface NodeRepository extends JpaRepository<Node, Long> {
@Query(value = "SELECT * FROM NODE WHERE parent_id = ?", nativeQuery = true)
List<Node> findNodesByParentId(Long parentId);
@Query(value = "SELECT * FROM NODE WHERE name = ?", nativeQuery = true)
Node findByName(String name);
}
意见/问题:
通过我的RestController,我可以通过调用此REST端点来获取第一级记录:
http://localhost:8080/myapp/api/nodes?name=Products
但是,它只提供了第一个级别(不是Books&Coffee和Latte下面的子节点):
不要在书中列出恐怖、浪漫、幻想,在咖啡中列出摩卡咖啡、拿铁咖啡(以及拿铁咖啡下的浓缩咖啡)
现在,如果我使用parentNode(例如Books),它确实会显示子级(但仅显示第一级):
JSON响应负载:
[
{
"id": 3,
"name": "Horror",
"parentId": 2
},
{
"id": 4,
"name": "Romance",
"parentId": 2
},
{
"id": 5,
"name": "Fantasy",
"parentId": 2
}
]
{
"id": 9,
"name": "Espresso",
"parentId": 8
}
尝试列出咖啡的所有子项时:
http://localhost:8080/myapp/api/nodes?name=Coffee
JSON响应负载:
[
{
"id": 7,
"name": "Mocha",
"parentId": 6
},
{
"id": 8,
"name": "Latte",
"parentId": 6
}
]
看,这一个没有显示浓缩咖啡,必须将拿铁作为父项调用才能显式查看:
http://localhost:8080/myapp/api/nodes?name=Latte
JSON响应负载:
[
{
"id": 3,
"name": "Horror",
"parentId": 2
},
{
"id": 4,
"name": "Romance",
"parentId": 2
},
{
"id": 5,
"name": "Fantasy",
"parentId": 2
}
]
{
"id": 9,
"name": "Espresso",
"parentId": 8
}
我可以获取特定子级的节点
如何使用递归获取所有级别的所有节点(我知道这将是一个不同的REST GET调用/REST端点)
需要使用递归来获取所有子级/子级,但不知道如何在这两种情况下(获取子节点和删除节点)都这样做。不知道为什么在这里不充分利用JPA,首先是在实体级,然后是在使用本机SQL而不是JPQL的查询过程中 1)如果您按照以下方式更改实体:
@Entity
public class Node {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@NotNull
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Node parentNode;
@OneToMany(mappedBy = "parentNode",
cascade = { CascadeType.DELETE, CascadeType.PERSIST} )
private List<Node> children;
}
顶级节点检索
@Override
@Transactional(readOnly = true)
public List<Node> getNodeHierarchy(Node inNode){
Node node = repository.findByName(inNode.getName());
traverseNodeAndFetchChildren(node);
return node.getChildren();
}
node.getChildren().size()
-这使得持久性上下文可以延迟加载@OneToMany
依赖项
4)将您的服务方法标记为
@Transactional(readOnly=true)
也可能是一个好主意,我建议您必须像建议一样实现
这是一个合乎逻辑的问题。而不是返回getHierarchyPerNode(子节点)代码>,则必须添加节点
public List<Node> getHierarchyPerNode(Node node) {
List<Node> nodes = new ArrayList<>();
List<Node> children = new ArrayList<>();
if (node != null) {
Node aNode = repository.findByName(node.getName());
nodes.add(aNode);
Long parentId = aNode.getId();
children = repository.findNodesByParentId(parentId);
// Was trying this as recursion but kept throwing an NullPointerException.
if (children != null) {
for (Node child : children) {
List<Node> childList = getHierarchyPerNode(child);
if (childList != null && !childList.isEmpty()) {
nodes.addAll(childList);
}
}
}
}
return nodes;
}
公共列表getHierarchyPerNode(节点){
列表节点=新的ArrayList();
List children=new ArrayList();
如果(节点!=null){
Node=repository.findByName(Node.getName());
添加(阳极);
长parentId=Anyone.getId();
children=repository.findNodesByParentId(parentId);
//尝试将其作为递归,但不断抛出NullPointerException。
如果(子项!=null){
用于(节点子节点:子节点){
List childList=getHierarchyPerNode(子节点);
if(childList!=null&!childList.isEmpty()){
nodes.addAll(childList);
}
}
}
}
返回节点;
}
您应该尝试中所述的具体化路径模型。您不需要手动添加任何子项,因为这些子项都在同一持久性上下文下运行。getChildren()实际上返回一个代理。在JPA/Hibernate中,当您对延迟加载的集合(例如size())调用任何方法时,持久性提供程序将自动加载该集合。我已经在getNodeHierarchy服务方法中添加了@Transactional,因为它遗漏了这一点。@Akash Shah关于这个问题有什么想法吗!
[
{
"id": 7,
"name": "Mocha",
"parentId": 6
},
{
"id": 8,
"name": "Latte",
"parentId": 6
}
]
http://localhost:8080/myapp/api/nodes?name=Latte
{
"id": 9,
"name": "Espresso",
"parentId": 8
}
@Entity
public class Node {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@NotNull
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Node parentNode;
@OneToMany(mappedBy = "parentNode",
cascade = { CascadeType.DELETE, CascadeType.PERSIST} )
private List<Node> children;
}
@Query(value = "select n from Node n inner join n.parentNode p where p.id = ?")
List<Node> findNodesByParentId(Long parentId);
@RequestMapping(
value = {"/api/nodes"},
method = RequestMethod.GET,
produces = "APPLICATION/JSON"
)
public ResponseEntity<Object> getNodeHierarchy(Node node) {
if (null == node) {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
List<Node> nodes = myService.getNodeHierarchy(node);
if (null == nodes) {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Object>(nodes, headers, HttpStatus.OK);
}
@Override
@Transactional(readOnly = true)
public List<Node> getNodeHierarchy(Node inNode){
Node node = repository.findByName(inNode.getName());
traverseNodeAndFetchChildren(node);
return node.getChildren();
}
public void traverseNodeAndFetchChildren(Node node) {
int size = node.getChildren().size();
if(size > 0){
for(Node childNode: node.getChildren()){
traverseNodeAndFetchChildren(childNode);
}
}
}
public List<Node> getHierarchyPerNode(Node node) {
List<Node> nodes = new ArrayList<>();
List<Node> children = new ArrayList<>();
if (node != null) {
Node aNode = repository.findByName(node.getName());
nodes.add(aNode);
Long parentId = aNode.getId();
children = repository.findNodesByParentId(parentId);
// Was trying this as recursion but kept throwing an NullPointerException.
if (children != null) {
for (Node child : children) {
List<Node> childList = getHierarchyPerNode(child);
if (childList != null && !childList.isEmpty()) {
nodes.addAll(childList);
}
}
}
}
return nodes;
}