Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I retrieve the foreign key from a JPA ManyToOne mapping without hitting the target table?

I have the following two annotated classes that I use to build a graph:

@Entity
@Table(name = "Edge")
public class Edge
{
    /* some code omitted for brevity */

    @ManyToOne
    @JoinColumn(name = "ixNodeFrom", nullable = false)
    private Node         _nodFrom;

    @ManyToOne
    @JoinColumn(name = "ixNodeTo", nullable = false)
    private Node         _nodTo;

    /* some code omitted for brevity */
}

@Entity
@Table(name = "Node")
public class Node
{
    /* some code omitted for brevity */

    @OneToMany(mappedBy = "_nodTo")
    private Set<Edge>    _rgInbound;

    @OneToMany(mappedBy = "_nodFrom")
    private Set<Edge>    _rgOutbound;

    /* some code omitted for brevity */
}

Now, when I build the graph, I issue two queries to fetch all rows from either table and set up the child / parent references, for which I need the ids stored in the Edge table.

Because I have defined the relation between the two tables in JPA, accessing the edge object to get the two nodes' ids triggers two SQL statements per edge, when the JPA provider lazily * loads the associated nodes. Since I already have the node objects, and the ids have already been loaded from the edge table, I want to skip those queries, as they take an awfully long time for larger graphs.

I tried adding these lines to the Edge class, but then my JPA provider wants me to make one mapping read-only, and I can't seem to find a way how to do that:

@Column(name = "ixNodeTo")
private long _ixNodeTo;

@Column(name = "ixNodeFrom")
private long _ixNodeFrom;

I'm using Eclipselink and MySQL, if it matters.


**The default behaviour for @ManyToOne actually is eager loading, see Pascal's answer*

like image 903
Hanno Fietz Avatar asked Oct 15 '10 11:10

Hanno Fietz


People also ask

Which method in JPA is used to retrieve the entity based on its primary keys?

The find() method used to retrieve an entity defined as below in the EntityManager interface. T find(Class<T> entityClass, Object primaryKey) – Returns entity for the given primary key. It returns null if entity is not found in the database.

What is difference between JPA unidirectional OneToOne and ManyToOne?

The main difference between a OneToOne and a ManyToOne relationship in JPA is that a ManyToOne always contains a foreign key from the source object's table to the target object's table, whereas a OneToOne relationship the foreign key may either be in the source object's table or the target object's table.


1 Answers

I got three good answers that were equally helpful, and by now none percolated to the top by public vote, so I'm merging them together here for a single comprehensive answer:

a) Change the query

You can load the whole graph at once by changing the query, thereby giving the JPA provider a chance to realize that it already has everything in memory and doesn't need to go back to the DB:

List<Node> nodes = em.createQuery(
        "SELECT DISTINCT n FROM Node n LEFT JOIN FETCH n._rgOutbound")
        .getResultList();

(via axtavt)

b) Use read-only fields for the FKs

Loading the FKs into their own fields, as described in the question, will also work if, as the JPA provider is demanding, the fields are declared to be readonly, which is done like this:

@Column(name = "ixNodeTo", insertable = false, updatable = false)

(via bravocharlie)

c) Use property access

If you are using property access instead of field access, the JPA provider also gets a chance to realize it already has the FK and doesn't need to fetch the referenced object. In short, property access means that you put the JPA annotations on the getter, thereby "promising" the JPA provider that your getter won't go and access the rest of the object. More details in this question. This will work for Hibernate, and for Eclipselink, it will work (assumed in the original answer, experimentally confirmed by me) with weaving enabled. (via Pascal Thivent)


Additionally, as Pascal points out in his answer, @ManyToOne, contrary to my original post, is not lazy-loading, but eager-loading by default, and changing that will require weaving as well.

like image 145
Hanno Fietz Avatar answered Oct 18 '22 23:10

Hanno Fietz