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

前端 未结 5 876
萌比男神i
萌比男神i 2020-12-29 08:07

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 brev         


        
相关标签:
5条回答
  • 2020-12-29 08:43

    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.

    0 讨论(0)
  • 2020-12-29 08:46

    How about using getReference()?

    For example:

    Node fkNode = em.getReference(edge.getNodeFrom()); // [1]
    fkNode.getId()
    

    [1] This will not trigger a SQL query to retrieve the nodeFrom

    0 讨论(0)
  • 2020-12-29 08:53

    Have you tried

    @Column(name = "ixNodeTo", insertable = false, updatable = false)
    
    0 讨论(0)
  • 2020-12-29 08:59

    I think you should try to optimize your query rather than change the mapping. For example, the following query fetches the whole graph at once (tested in Hibernate):

    List<Node> nodes = em.createQuery(
                "SELECT DISTINCT n FROM Node n LEFT JOIN FETCH n._rgOutbound")
                .getResultList();
    
    0 讨论(0)
  • 2020-12-29 09:00

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

    In theory, a JPA provider should be able to not trigger a query when calling

    someEdge.getNodeFrom().getId()
    

    as it already has the id (as FK).

    I'm 100% sure Hibernate can (assuming you're using property access). In the case of EclipseLink, I don't know (if it does, it will probably requires weaving).

    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.

    Note that @ManyToOne uses an EAGER strategy by default. If you want to make it LAZY, you have to decalre it explicitly (but again, this will require weaving of your classes with EclipseLink).

    0 讨论(0)
提交回复
热议问题