Lazy loading of #OneToOne relation with Hibernate - java

I have an entity with #OneToOne association. Like this:
#Entity
public class Person {
#Id Long id;
#OneToOne(fetch = FetchType.LAZY) Address address;
}
When I load such entity Hibernate ignores LAZY. Vlad explains:
Lazy loading works except for the parent side of a #OneToOne association. This is because Hibernate has no other way of knowing whether to assign a null or a Proxy to this variable.
Similar statement here. I don't get it. There is FK column PERSON.ADDRESS_ID and in case there is any value Hibernate should know Proxy should be used. Am I missing something?
UPADTE:
My original code was in Kotlin. I have tried to create the same example in Java and surprisingly lazy loading works fine there.

Try to understand it using #OneToMany relationship.
When you have that, you specify some collection i.e List, for example we have an entity
class A {
#OneToMany
List<B> bs;
public List<B> getBs() {
return bs;
}
}
So when hibernate loads the A, it is able to identify that you have List<B> and you may call getBs() just after the class is loaded so hibernate creates a wrapper list which doesn't have any B yet and it will wait until you perform any operation on the list ie. iterate, add etc.
As soon as you perform the operation, hibernate will issue the query and load the objects into the set, hence lazy loading works fine here.
That's why one-to-many by default is lazy
Now let's take example of #OneToOne
class A {
#OneToOne
B b;
public B getB() {}
}
When hibernate loads A, it will see that user may call the getB just after A is loaded, so it needs to initialise B as well.
Now, even if B supports proxy, hibernate have to initialise it with proxy or null and how that decision will be made, it will have to query the B to check if it exists or not but if it queries just to check, why just check only, why not initialise it fully, hence it does it eagerly, ignoring the Lazy attribute1
But this is not true for child side, if you specify the #One-To-One on child side
class B {
#OneToOne(lazy)
A a;
public A getA() {}
}
Because this is the entity for table which holds the foreign key to A entity table, hibernate will initialise it with the proxy of A because hibernate knows that this entity is child entity and has foreign key associated, so it can lazy load when required, if it's null, you would get null A.
Correction:
The above behaviour is obvious for the optionable relation (optional = true) as I have already explained and you may find other answers stating that, but it is not obvious when you use optional=false.
With non-optional relation, we would think that hibernate identify that there would be a child present for the parent so hibernate will initialise the proxy and it should depict the lazy loading behaviour.
But to even initialise the proxy, hibernate will need minimum information like identifier and it would need to query from the child table, hence it becomes the same case as optional relation and loads eagerly.
There is one solution to still make it work though (at least I thought so), that if you share the primary key of your parent entity with the child entity using #MapsId
class A {
#Id
private Integer id;
#OneToOne(fetch = Lazy, mappedBy = "a", optional = false)
private B b;
}
class B {
#Id
private Integer id;
#OneToOne
#MapsId
private A a;
}
This should have worked, because now you are sharing the parent primary key with the child and hibernate now knows the identifier and it doesn't need to query it from the table and should be able to initialise the proxy easily.
However, it doesn't work and still loads eagerly which is strange and after a little digging, I found this issue reported by Vlad himself.2
Although I found a workaround in related issues and have also asked on the above issue about it if that is a valid one, that's why not posting here.
1Some of the older version of hibernate does support the lazy loading from parent side as well but that behaviour is removed in recent versions because they needed to check the existence of the child.
2I checked this behaviour using hibernate version 5.4.8.Final and 5.4.30.Final

Related

JPA Annotations and mappedBy

I am working on java application and using JPA to interact with the database, I have two important questions:
I want to make a bi-directional link between two classes since I need to access the data on both sides. Let's take the case of two classes A and B with A *-1 B (as UML diagram, A has an unique B and B has several A ..).
in A:
#ManyToOne
private B attribute;
in B
#OneToMany
private List<A> list;
Is that enough to make the two-way link? or it is mandatory to use the mappedBy?
Which brings us to my second question, if the mappedBy is placed on the wrong side, it'll just impact the performance or even worse ? (data not persisted)? For example in the case of the cardinality 1-* we have no choice, the mappedBy should be in the side of OneToMany and in this case:
in B
#OneToMany(mappedBy = "attribute")
private List<A> list;
Knowing the fact that I will not create the class B, and create a List and assign objects, I will do nothing in the side B. I'll just create repeatedly classes A and every time I assign it an B object, so I may have several classes A that have the same affected object B and I want that B automatically updates this link and its list of A.
Is that enough to make the two-way link? or it is mandatory to use the
mappedBy?
For a Bi-directional relationship it is mandatory .
Which brings us to my second question, if the mappedBy is placed on
the wrong side, it'll just impact the performance or even worse ?
(data not persisted)?
Worse - It will not work but it will not be silent fail you will see exceptions.
Having said that is simple to understand . It goes with #OneToMany .
This might help you understanding this more.
mappedBy should be added to the entity which does not have a foreign key in its table (most likely B in this case).
If mappedBy is on the wrong side, you should see an exception.
Is that enough to make the two-way link? or it is mandatory to use the
mappedBy?
Not quite. You do need the MappedBy attribute for bidirectional many-to-one relationships on the inverse side - that is the side that does not have the foreign key, and is always the one side in a many-to-one relationship. You also need joincolumn information on the many side of the relationship.
So in summary:
Many-to-one side – the owning side of the relationship - #JoinColumn information is on this side.
This needs to be specified in both uni-directional and bidirectional relationships
One-to-many side – the inverse side – mappedBy Attribute on this side.
This needs to be specified if the relationship is bidirectional.
#Entity
public class A ……..
//Owning side of the relationship with the #JoinColumn annotation.
#ManyToOne
// Assume TABLEPK column holds PK of B's table
#JoinColumn (name = "TABLEBPK")
private B attribute;
#Entity
public class B ……
//Inverse side of the relationship with the MappedBy attribute.
#OneToMany(MappedBy = “attribute”)
private List<A> list;
Which brings us to my second question, if the mappedBy is placed on
the wrong side, it'll just impact the performance or even worse ?
It won't work. Just put it on the inverse side of the relationship.
Knowing the fact that I will not create the class B, and create a List
and assign objects, I will do nothing in the side B. I'll just create
repeatedly classes A and every time I assign it an B object, so I may
have several classes A that have the same affected object B and I want
that B automatically updates this link and its list of A.
In this scenario, you create a class A, with the attribute field populated with an instance of B. Now when you persist A - a new instance - it will contain an instance of B in the attribute field that may or may not be new. We want JPA to persiste A and then navigate accross the relationship and persist B also. If B already exists in the perssitence context then ignore it. Adding CascadeType.PERSIST will achieve this.
#Entity
public class A ……..
//Owning side of the relationship with the #JoinColumn annotation.
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn (name = "TABLEBPK")
private B attribute;
These guys write really well on this stuff....
"Pro JPA 2 Mastering the Java™ Persistence API" by Mike Keith and Merrick Schnicariol."

JPA throwing "multiple assignments to same column" during save operation

I have a model class that references another model class and seem to be encountering an issue where the #OneToOne annotation fixes one problem but causes another. Removing it causes the inverse.
JPA throws "multiple assignments to same column" when trying to save changes to model. The generated SQL has duplicate columns and I'm not sure why.
Here's a preview of what the classes look like:
The parent class references look like this:
public class Appliance {
public Integer locationId;
#Valid
#OneToOne
public Location location;
}
The child Location class has an id field and a few other text fields -- very simple:
public class Location {
public Integer id;
public String name;
}
When I attempt to perform a save operation, does anyone know why JPA is creating an insert statement for the Appliance table that contains two fields named "location_id"?
I need to annotate the reference to the child class with #OneToOne if I want to be able to retrieve data from the corresponding database table to display on screen. However, If I remove #OneToOne, the save works fine, but it obviously won't load the Location data into the child object when I query the db.
Thanks in advance!
It appears you did not define an #InheritanceType on the parent Class. Since you did not, the default is to combine the the parent and the child class into the same Table in the Single Table Strategy.
Since both entities are going into the same table, I think that #OneToOne is trying to write the id twice - regardless of which side it is on.
If you want the parent to be persisted in its own table, look at InheritanceType.JOINED.
Or consider re-factoring so that you are not persisting the parent separately as JOINED is not considered a safe option with some JPA providers.
See official Oracle Documentation below.
http://docs.oracle.com/javaee/7/tutorial/doc/persistence-intro002.htm#BNBQR
37.2.4.1 The Single Table per Class Hierarchy Strategy
With this strategy, which corresponds to the default InheritanceType.SINGLE_TABLE, all classes in the hierarchy are mapped to a single table in the database. This table has a discriminator column containing a value that identifies the subclass to which the instance represented by the row belongs.
In OpenJPA, according to the docs (http://openjpa.apache.org/builds/1.0.1/apache-openjpa-1.0.1/docs/manual/jpa_overview_mapping_field.html), section 8.4, the foreign key column in a one-to-one mapping:
Defaults to the relation field name, plus an underscore, plus the name
of the referenced primary key column.
And the JPA API seems to concur with this (http://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html)
I believe this means that in a one-to-one mapping, the default column name for properties in a dependent class is parentClassFieldName_dependentClassFieldName (or location_id in your case). If that's the case, the location_id column you are defining in your Appliance class is conflicting with the location_id default column name which would be generated for your Location class.
You should be able to correct this by using the #Column(name="someColumnName") annotation and the #JoinColumn annotation on your #OneToOne relationship to force the column name to be something unique.
Ok gang, I figured it out.
Here's what the new code looks like, followed by a brief explanation...
Parent Class:
public class Appliance {
public Integer locationId;
#Valid
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="location_id", referencedColumnName="id")
public Location location;
}
Child Class:
public class Location {
public Integer id;
public String name;
}
The first part of the puzzle was the explicit addition of "cascade = CascadeType.ALL" in the parent class. This resolved the initial "multiple assignments to same column" by allowing the child object to be persisted.
However, I encountered an issue during update operations which is due to some sort of conflict between EBean and JPA whereby it triggers a save() operation on nested child objects rather than a cascading update() operation. I got around this by issuing an explicit update on the child object and then setting it to null before the parent update operation occurred. It's sort of a hack, but it seems like all these persistence frameworks solve one set of problems but cause others -- I guess that's why I've been old school and always rolled my own persistence code until now.

Grails. Hibernate lazy loading multiple objects

I'm having difficulties with proxied objects in Grails.
Assuming I've got the following
class Order {
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="xxx", joinColumns = {#JoinColumn(name = "xxx")}, inverseJoinColumns = {#JoinColumn(name = "yyy")})
#OrderBy("id")
#Fetch(FetchMode.SUBSELECT)
private List<OrderItem> items;
}
class Customer {
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "xxx",insertable = false, nullable = false)
private OrderItem lastItem;
private Long lastOrderId;
}
And inside some controller class
//this all happens during one hibernate session.
def currentCustomer = Customer.findById(id)
//at this point currentCustomer.lastItem is a javassist proxy
def lastOrder = Order.findById(current.lastOrderId)
//lastOrder.items is a proxy
//Some sample actions to initialise collections
lastOrder.items.each { println "${it.id}"}
After the iteration lastOrder.items still contains a proxy of currentCustomer.lastItem. For example if there are 4 items in the lastOrder.items collection, it looks like this:
object
object
javassist proxy (all fields are null including id field). This is the same object as in currentCustomer.lastItem.
object
Furthermore, this proxy object has all properties set to null and it's not initialized when getters are invoked. I have to manually call GrailsHibernateUtils.unwrapIdProxy() on every single element inside lastOrder.items to ensure that there are no proxies inside (which basically leads to EAGER fetching).
This one proxy object leads to some really weird Exceptions, which are difficult to track on testing phase.
Interesting fact: if I change the ordering of the operations (load the order first and the customer second) every element inside lastOrder.items is initialized.
The question is: Is there a way to tell Hibernate that it should initialize the collections when they are touched, no matter if any elements from the collection is already proxied in the session?
I think what's happening here is an interesting interaction between the first level cache (stored in Hibernate's Session instance) and having different FetchType on related objects.
When you load Customer, it gets put in to the Session cache, along with any objects that are loaded with it. This includes a proxy object for the OrderItem object, because you've got FetchType.LAZY. Hibernate only allows one instance to be associated with any particular ID, so any further operations that would be acting on the OrderItem with that ID would always be using that proxy. If you asked the same Session to get that particular OrderItem in another way, as you are by loading an Order containing it, that Order would have the proxy, because of Session-level identity rules.
That's why it 'works' when you reverse the order. Load the Order first, it's collection is FetchType.EAGER, and so it (and the first level cache) have fully realized instances of OrderItem. Now load a Customer which has it's lastItem set to one of the already-loaded OrderItem instances and presto, you have a real OrderItem, not a proxy.
You can see the identity rules documented in the Hibernate manual:
For objects attached to a particular Session... JVM identity for database identity is guaranteed by Hibernate.
All that said, even if you get an OrderItem proxy, it should work fine as long as the associated Session is still active. I wouldn't necessarily expect the proxy ID field to show up as populated in the debugger or similar, simply because the proxy handles things in a 'special' way (ie, it's not a POJO). But it should respond to method calls the same way it's base class would. So if you have an OrderItem.getId() method, it should certainly return the ID when called, and similarly on any other method. Because it's lazily initialized though, some of those calls may require a database query.
It's possible that the only real problem here is simply that it's confusing to have it so that any particular OrderItem could be a proxy or not. Maybe you want to simply change the relationships so that they're either both lazy, or both eager?
For what it's worth, it's a bit odd that you've got the ManyToMany relationship as EAGER and the ManyToOne as LAZY. That's exactly the reverse of the usual settings, so I would at least think about changing it (although I obviously don't know your entire use case). One way to think about it: If an OrderItem is so expensive to fetch completely that it's a problem when querying for Customer, surely it's also too expensive to load all of them at once? Or conversely, if it's cheap enough to load all of them, surely it's cheap enough to just grab it when you get a Customer?
I think you can force eager loading this way or using
def lastOrder = Order.withCriteria(uniqueResult: true) {
eq('id', current.lastOrderId)
items{}
}
or using HQL query with 'fetch all'

#ManyToOne and #BatchSize

I found in some old code strange thing (at least for me).
The field which is annotated #ManyToOne is also annotated with #BatchSize.
I always thought that #BatchSize annotation only affects when annotated at class level or on a collection (#OneToMany) and affects pre-fetching when iterating.
But maybe I am wrong and annotating #ManyToOne with #BatchSize affects something. I can't find the answer in the documentation.
Does annotating #ManyToOne with #BatchSize have sense?
I think the question refers to combining #ManyToOne and #BatchSize on the same field, e.g.:
#ManyToOne
#BatchSize(size = 5)
private User owner;
This use case is not supported by Hibernate, at least when using annotations. The only uses of batch fetching mentioned by the documentation are:
On collection fields, i.e., #OneToMany or #ManyToMany (but not #ManyToOne)
On the entity class to be fetched
E.g.:
#Entity
#BatchSize(size = 5)
public class User {
...
}
This latter case enables batching for all relationships of type User, including many-to-one relationships. However, with the annotation on the entity class it is not possible to control the behaviour on a field-by-field basis.
A search through the Hibernate source code for all uses of #BatchSize confirms the lack of support for your usage. From what I see in AnnotationBinder.java, the #BatchSize annotation is only inspected on the entity class and on fields which have some kind of #XxxToMany annotation.
#ManyToOne associated with #BatchSize could make sense only if the corresponding field is marked as lazy (lazy=true).
Indeed, if the field is not lazy, it's by definition already loaded since the enclosing entity is loaded, so the problem of database calls doesn't apply.
Imagine a Person class who has a collection of ShoesPair element (ShoesPair.class) and within this one is present an owner field marked as lazy (since optional and not really bringing an important information when retrieving a specific pair of shoes).
One wants to iterate through 25 pair of shoes (25 ShoesPair objects) in order to retrieve their owner.
If the owner field (corresponding to one person) is only annotated with #ManyToOne, there would be 25 select to database.
However, if annoted with #BatchSize(size=5), there would be merely 5 calls and so increasing performance.
From the Hibernate documentation, it is precised that batch size does not only apply with collections:
You can also enable batch fetching of collections.
Hibenate mentions especially #OneToMany cases, because these one are applied with fields that are in 90% of cases marked as lazy.
Solving N+1 query problem with Hibernate
1 Using Criteria queries with fetchMode
Criteria criteria = session.createCriteria(Customer.class);
criteria.setFetchMode("contact", FetchMode.EAGER);
2 HOL fetch join
3 #BatchSize
The #BatchSize annotation can be used to define how many identical associations to populate in a single database query. If the session has 100 customers attached to it and the mapping of the 'contact' collection is annotated with #BatchSize of size n. It means that whenever Hibernate needs to populate a lazy contact collection it checks the session and if it has more customers which their contact collections need to be populated it fetches up to n collections.
#OneToMany(mappedBy="customer",cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#BatchSize(size=25)
private Set<Contact> contacts = new HashSet<Contact>();

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*
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

Categories

Resources