JPA构建组织结构:首节点父节点为空触发PSQLException问题
Hey there, let's figure out why you're hitting that PSQLException when inserting the first node. I've gone through your code and spotted a few key issues that are causing this problem. Let's break them down one by one:
1. Bidirectional Association Mapping Conflict
Your @OneToMany and @ManyToOne annotations are both using @JoinColumn(name = "parent_id"), which creates a mapping conflict. In a bidirectional parent-child relationship, the @ManyToOne side is the owning side (it controls the foreign key), while the @OneToMany side should use mappedBy to reference the owning field instead of defining its own @JoinColumn.
Fix the Node entity mapping:
@Entity @Table(name = "tree") public class Node implements Serializable { @Id private int id; private String name; // Use mappedBy to point to the "parent" field in the child Node @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true) @OrderColumn @JsonManagedReference private Set<Node> children = new HashSet<>(); @ManyToOne(fetch = FetchType.LAZY, optional = true) @JoinColumn(name = "parent_id", nullable = true) @JsonBackReference private Node parent; // Correct the addChild method logic public void addChild(Node child) { this.children.add(child); child.setParent(this); } // Getters and setters... }
Note: I renamed the method to addChild for clarity, and added orphanRemoval = true to automatically remove child nodes if they're detached from the parent.
2. Broken Association Logic in addNode
Your original addNode method was trying to add the node to its own parent's children, which makes no sense for building a hierarchy. The correct logic is: when you add a child to a parent, you add the child to the parent's children set and set the child's parent reference to the parent itself.
3. Incorrect Controller Logic for Associating Nodes
Your current controller code calls node.addNode(node) on every node—this is essentially trying to add each node as a child of itself, which is totally wrong. Instead, you need to first save all nodes, then associate each child with its parent properly.
Updated Controller Code:
@RestController @RequestMapping("/node") public class NodeController { @Autowired private NodeRepository nodeRepository; @GetMapping public @ResponseBody Iterable<Node> index() { return nodeRepository.findByParentIsNull(); } @PostMapping public String add(@RequestBody List<Node> nodes) { // First save all nodes to get them persisted in the database List<Node> savedNodes = nodeRepository.saveAll(nodes); // Now associate each child with its parent for (Node node : savedNodes) { if (node.getParent() != null && node.getParent().getId() != 0) { // Find the matching parent from the saved nodes list Node parent = savedNodes.stream() .filter(n -> n.getId() == node.getParent().getId()) .findFirst() .orElseThrow(() -> new RuntimeException("Parent node not found: " + node.getParent().getId())); parent.addChild(node); } } // Save the updated parent-child associations nodeRepository.saveAll(savedNodes); return "nice"; } }
4. Fix the Root Node's Parent in the Request Body
Your sample data sends parent:{} for the root node, which tells JPA to create an empty Node object instead of setting parent to null. Change the root node's parent field to null in your request:
[ { "id":8, "name":"Top Parent", "parent":null }, { "id":9, "name":"Node 1", "parent":{ "id":8 } }, { "id":10, "name":"Node 2", "parent":{ "id":8 } }, { "id":11, "name":"Node 3", "parent":{ "id":9 } } ]
Why This Works
- The corrected mapping ensures JPA correctly handles the
parent_idforeign key, allowing it to benullfor root nodes. - The fixed
addChildmethod properly establishes the parent-child relationship without circular references. - The controller now saves all nodes first, then links children to their parents—this avoids issues with unsaved parent entities that would cause database constraints to fail.
- Sending
parent:nullinstead of an empty object tells JPA explicitly that the root node has no parent, so it insertsnullinto theparent_idcolumn as expected.
Give these changes a try—this should resolve the PSQLException and let you insert the entire hierarchy correctly in one go.
内容的提问来源于stack exchange,提问作者Febrinanda Endriz Pratama

