In this specific scenario, say I have a couple of tables {A,B} and their generated JPA classes. There is a FK relationship on a column X from Table A -> Table B and this column X in Table A has been annotated with
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "X", referencedColumnName = "X", insertable = false, updatable = false).
I have a Named JPA Query which is joining the two tables on this column "X". On execution, I get the correct results,but multiple queries are being run against the DB. On
the join's each data match, it appears a query is being generated and run against the DB. This is overburdening our Database. From my understanding, on a LAZY fetch, isn't JPA supposed to execute the master query only once and bring back the entire result-set, instead of spawning additional child queries for join's data match
You shouldn't have to do a join query to get the data. If you specify OneToOne, Hibernate associates the two objects. LAZY means that the request to get the linked object will only be launched when you need that object.
Related
As I can see from generated SQL while querying entities with collection mapped like this:
#ElementCollection(targetClass = Long.class, fetch = FetchType.EAGER)
#CollectionTable(name = "LIST_GAMES",
joinColumns = #JoinColumn(name = "LIST_ID", referencedColumnName = "ID"))
#Column(name = "GAME_ID")
#OrderColumn(name = "GAME_ORDER")
private List<Long> gameIds;
Hibernate doesn't add any 'order by' statements to it. So does it mean, that collection is sorted in-memory after loading? Or does it means that I need a complex index on db level like (LIST_ID, GAME_ORDER) to handle order by in the db?
UP: My question is actually HOW hibernate sorts this collection? Is It handled by hibernate in-memory or its retrieved already sorted from db? And am I need to create database index for GAME_ORDER column?
OrderBy adds an order by clause to the generated SQL to order the members of the retrieved collection by a column of the table of the target entity:
#OrderBy("GAME_ORDER ASC")
public List<Long> gameIds;
will generate a SQL query like
select ... order by game.game_order ASC
#OrderColumn defines the name of an additional column in the table, containing the index of the entity in the list.
If you change the ordering of the elements in the list, Hibernate will change the value of this column. And if your gameId have 0, 3, and 5 as values in this column, the list will be
[gameIds0, null, null, gameIds3, null, gameIds5]
see here
As per Oracle Doc #OrderColumn is:
Specifies a column that is used to maintain the persistent order of a
list. The persistence provider is responsible for maintaining the
order upon retrieval and in the database. The persistence provider is
responsible for updating the ordering upon flushing to the database to
reflect any insertion, deletion, or reordering affecting the list.
So it is likely that the collection is sorted after the data is retrieved from the database and it is done in memory.
I have a class called LocType which has 30 elements and a #OneToMany relation to LocProfile which has 40 elements. I have to do some calculations to figure which LocTypeProfile should be associated once a user picks a location, so it needs to be available at jsp side.
If I define the relationship for Eager initialization like below, when I retrieve elements from LocType it gives me 30 elements with the instances where one to many relationship stored in the list - which is the behavior I want.
#OneToMany(fetch = FetchType.EAGER, mappedBy = "LocType ", cascade = CascadeType.ALL)
private List<LocTypeProfile > locTypeProfiles;
Now If I change the mapping to Lazy loading in the interest of good practice
#OneToMany(mappedBy = "creditTypes")
and try to query using a fetch
select lt from LocTypes lt left join fetch lt.locTypeProfiles
I get the 40 elements, where the data is flattened.
My question is is there a way to replicate the behavior of the Eager initialization in this scenario?
When doing a join fetch for a OneToMany relationship you need to use distinct in the select statement, otherwise an entity will be created for each row fetched.
Try:
select distinct lt from LocTypes lt left join fetch lt.locTypeProfiles
Reference: One-To-Many relationship gets duplicate objects whithout using “distinct”.Why?
I have a table that has no associated Entity. I need to write JPQL query which will filter by that table's fields. Is it possible?
I know that I can do one of the following:
Create an Entity for that table( but it's basically a join table, so it will look strange to create an Entity class for it)
Write a native query( I don't like this approach either. If I use JPA, I must use JPQL only).
Create fully functional ManyToMany mapping( I just don't need it).
Could there be another approach?
Unfortunately you cannot do that with JPQL.
You should use SQL.
But a native query can also return Entities. Either if the returned values matches the entity or using #SqlResultSetMapping as described here:
http://javaee.support/sample/jpa-native-sql-resultset-mapping/
If you only need to join to entities on a relationship that is not mapped JPA 2.1 is able to JOIN on any table columns.
The problem was that I did not need a real many-to-many object mapping but only collection of id's in my only entity. So I came to following solution:
#ElementCollection
#CollectionTable(
name="user_to_feed",
joinColumns = #JoinColumn(name = "feed_id",referencedColumnName = "id")
)
#Column(name="user_id")
private List<Integer> userIds = new ArrayList<>();
This allows me to make following query:
select f.url from Feed f join f.userIds u where :id in u
I have a domain object that holds a collection of another object through a #ManyToMany annotaion:
#ManyToMany(fetch=FetchType.LAZY,cascade = { CascadeType.MERGE, CascadeType.PERSIST})
#JoinTable(name = "join_table", joinColumns=#JoinColumn(name="a_id", referencedColumnName = "a_id"), inverseJoinColumns=#JoinColumn(name = "b_id", referencedColumnName = "b_id"))
private List<B> BsList;
In the join table i hold additional data columns.
I noticed that when i work with the object that holds the list and call setBsList() the data i had in the additional columns is deleted.
Does Hibernate re-write the rows in the join table each time?
In the join table i hold additional data columns.
If you have additional data columns, it's not a join table. It's a table with two FKs which can also be PKs. And Hibernate is doing the right thing. So, you should instead create another entity representing this "fake join table", and map it accordingly.
You shouldn't call setBsList(), you should directly modify getBsList() otherwise Hibernate has no way of tracking what has changed.
When loading, the actual List you get is a Hibernate-specific implementation which deals with the lazy loading and tracks what is added and removed, so that it can be correctly updated. If you replace this with a different list implementation it will delete the old one and only add then only add the new entries. When using Hibernate with collections, it's a good idea to make the setter protected so you can't accidentally do this.
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*
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.
Have you tried
#Column(name = "ixNodeTo", insertable = false, updatable = false)
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).
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();
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