JPA 2.0: How to get the whole entity graph

I am using JPA 2.0 with OpenJPA as the main implementation. I have an object that maps itself to represent the parent-child hierarchy between entities. An entity can have more than one child, but not more than one parent. Thus, an entity without a parent is at the top of the hierarchy. My goal is to extract all hierarchies from a data table. Therefore, I have a query like:

SELECT e FROM MyEntity e where e.parent is null 

In MyEntity, I did a mapping like:

 @ManyToOne @JoinColumn(name="PARENT") private MyEntity parent; @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) private List<MyEntity> children; 

When the program starts, the objects at the top of the hierarchy are filled with all their children, but the children do not have children. I saw that EAGER fetch fills the entire graph of the object. But this is not so. JPA 2.1 has an EntityGraph ASAIK function. But how can this be achieved in JPA 2.0?

+5
source share
2 answers

EAGER works only on one level, and it is not intended to receive tree structures.

I suggest you change the one-to-many choice to LAZY, because choosing EAGER is the smell of code and choosing a root entity is:

 SELECT e FROM MyEntity e LEFT JOIN FETCH e.children where e.parent is null 

You use recursion to get the whole graph with additional subsamples.

 void fetchAll(MyEntity root) { for(MyEntity child : root.children) { fetchAll(child); } } 

A more efficient approach is to completely remove the children’s collection and use the recursive CTE in the FK association to retrieve all the identifiers of all objects in a given tree. Then, with the second JPA request, you retrieve all the objects by their identifiers and restore the tree corresponding to the parents .

Update using an actual solution

I added a test on GitHub to provide a solution for this. Given the following object:

 @Entity(name = "Node") public class Node { @Id @GeneratedValue private Long id; @ManyToOne @JoinColumn(name = "parent_id") private Node parent; //This must be transient as otherwise it will trigger an additional collection fetch //@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) @Transient private List<Node> children = new ArrayList<>(); public Node() {} public Node getParent() { return parent; } public List<Node> getChildren() { return children; } public void addChild(Node child) { children.add(child); child.parent = this; } } 

The next transformer can restore the whole tree as you want

 Node root = (Node) doInHibernate(session -> { return session .createSQLQuery( "SELECT * " + "FROM Node " + "CONNECT BY PRIOR id = parent_id " + "START WITH parent_id IS NULL ") .addEntity(Node.class) .setResultTransformer(new ResultTransformer() { @Override public Object transformTuple(Object[] tuple, String[] aliases) { Node node = (Node) tuple[0]; if(node.parent != null) { node.parent.addChild(node); } return node; } @Override public List transformList(List collection) { return Collections.singletonList(collection.get(0)); } }) .uniqueResult(); }); 
+1
source

Source: https://habr.com/ru/post/1238710/


All Articles