Home > Back-end >  Is there a way to perform a single-query JOIN FETCH "with the same entity twice"?
Is there a way to perform a single-query JOIN FETCH "with the same entity twice"?

Time:10-10

I have something like:

    @Entity
    public class Edge {
       @ManyToOne(fetch = FetchType.EAGER)
       @Fetch(value = FetchMode.JOIN)
       @JoinColumn(name = "start_node", referencedColumnName = "id")
       Node start;
       
       @ManyToOne(fetch = FetchType.EAGER)
       @Fetch(value = FetchMode.JOIN)
       @JoinColumn(name = "end_node", referencedColumnName = "id")
       Node end;
    }

Invoking the findAll() method provided by the JPA connector I would like to obtain a (single) query of the form:

SELECT * FROM EDGE edge, NODE start, NODE end WHERE edge.start_id = start.id AND edge.end_id = end.id

Instead, the framework first performs a single query to obtain all the edges, then performs multiple queries (one for edge, ideally) for the nodes. I am an hibernate-begginer. I think this is a very common question. I have found a lot of material related to the problem, but no proper answer.

CodePudding user response:

In short, JPA ignores Hibernate FetchMode.JOIN by default. The reason it does this is JPA fetches entities lazily (only when you need it, e.g. when you call getStart()). As a result, the application reserves resources to only when you need it.

Eager fetching is usually a bad choice and should be avoided, unless you really need to. I would suggest reading this thread as it lists much of the reading materials related to that.

In a nutshell, what you could do is use @NamedEntityGraph to define eager-fetching of your entities:

@Entity
@Table(name = "edge")
@NamedEntityGraph(name = "Edge.nodes", attributeNodes = {
        @NamedAttributeNode("start"),
        @NamedAttributeNode("end")
}) // This is the important annotation
public class Edge {
    @EmbeddedId
    private EdgeKey id = new EdgeKey();

    private int value;

    @ManyToOne
    @MapsId("startId")
    @JoinColumn(name = "start_id", referencedColumnName = "id")
    private Node start;

    @ManyToOne
    @MapsId("endId")
    @JoinColumn(name = "end_id", referencedColumnName = "id")
    private Node end;

    // getters, setters...
}

@Embeddable
public class EdgeKey implements Serializable {
    @Column(name = "start_id", nullable = false)
    Integer startId;

    @Column(name = "end_id", nullable = false)
    Integer endId;

    // getters, setters, equals and hash code...
}

Here, you specify the attributes that you need to fetch eagerly (start and end). To read more about this, you can see here and here.

Furthermore, you annotate your JPA repository method with @EntityGraph:

@Repository
public interface EdgeRepository extends JpaRepository<Edge, Integer> {
    @Override
    @EntityGraph(value = "Edge.nodes", type = EntityGraph.EntityGraphType.LOAD)
    List<Edge> findAll(); // You override the default implementation of findAll()

    @EntityGraph(value = "Edge.nodes", type = EntityGraph.EntityGraphType.LOAD)
    Edge getByValue(int value); // other example
}

This will result in a query that's similar to this:

SELECT edge0_.end_id AS end_id1_0_0_,
       edge0_.start_id AS start_id2_0_0_,
       node1_.id AS id1_1_1_,
       node2_.id AS id1_1_2_,
       edge0_.value AS value3_0_0_,
       node1_.value AS value2_1_1_,
       node2_.value AS value2_1_2_
FROM edge edge0_
LEFT OUTER JOIN node node1_ ON edge0_.start_id=node1_.id
LEFT OUTER JOIN node node2_ ON edge0_.end_id=node2_.id
  • Related