I know that lazy loading can't be done if you have a two-way relationship in a one-to-one relationship.
So I read about How can I make a JPA OneToOne relation lazy.
The article says that you can't create a proxy in a one-to-one relationship, but I can't quite understand it.
I think There is no difference between a many-to-one relationship and a one-to-one relationship. He says that in a many-to-one relationship you can create a proxy because you can get the value from FK.
However, one-to-one relationships can also get values with FK, there is no explanation as to why exactly one-to-one relationships can't only get values.
Can someone please explain clearly?
// one to one
class Address {
..
#OnetoOne(fetch = LAZY)
#JoinColumn(~)
private Order order;
}
class Order {
..
#OnetoOne(mappedBy = order, fetch = LAZY)
private Address address; // LAZY not wokring
}
// ManyToOne
public class Member {
#ManyToOne(fetch = LAZY)
#JoinColmn(~)
public Team team; // LAZY working!
}
I think these two are different. I know the PK values equally, so why doesn't only the one-to-one relationship do lazy loading?
Address and Member are the same. They own the column representing the association. Let's say, for example, the column team_id in the table Member and the column order_id in the table Address.
When Hibernate ORM reads a row from the table Member, by checking the value of the team_id column, it knows if that value is null or if there is an association. Therefore, when the entity is created it can decide if the value mapping the association (Mapping#team)is null or a proxy. In this case lazy fetching is possible.
The table Order doesn't have a column representing the association, that's because the association is mapped by the column order_id in Address. When Hibernate ORM needs to convert a row in the table Order to an entity, it also needs to check the table Address to see if an association exists. And depending on the result, it will set the value Order#address to null or the associated address. In this case, it cannot know if the value of the association is null or not without checking the table Address: lazy fetching is not possible.
Note that it might still be possible to lazy fetch the association when the mapping states that the association is never null: #OneToOne(mappedBy="order", optional=false).
Basically, the problem is that if a value can be mapped to null, Hibernate ORM needs to check first if this value is actually null because, otherwise, code like if (order.address == null) { is not going to work if a proxy is created.
Empty *-to-many associations are different in this regard because Hibernate ORM maps them with an empty collection (and never with null) and this means that it can always return a proxy.
Related
It is true that using the relationship manytomany is easier than presenting the intermediate table by an entity but I don't like this annotation for many raisons:
It's impossible to add a new columns in my intermediate table
matching the intermediate table by entity looks more close to the reality than using #ManyToMany
What do you think ?
It's a case to case basis.
1) There are occasions when a relationship actually has "state" associated with it. In the case of many-to-many relationship, if you want to add a state/property in your relationship, then you will need turn your relationship into a separate entity. Hence, this will result in new column(s) in your join table besides the foreign keys.
This requires updating your mappings. Here is a good example:
http://www.mkyong.com/hibernate/hibernate-many-to-many-example-join-table-extra-column-annotation/
Technically, we are not using the #ManyToMany mapping here but logically it is many-to-many relationship. The new entity (relationship entity) will have a many-to-one relationship to each of the existing entity types, and each of the entity types will have a one-to-many relationship back to the new entity representing the relationship
2) If maintaining a relationship "state" as mentioned in point #1 is not necessary, then a simple use of #ManyToMany association mapping should suffice. It eliminates the need to maintain/manage a separate entity just to represent the relationship. Otherwise, a separate entity will just complicate your object model.
I was always told that in case of a bidirectional relationship that you remove your entity as follows:
Breakdown relationships
Update owner entity
Remove entities
But now my primary key is part of the relationship, so this isn't possible anymore.
Does anyone have an idea about how to remove the entity with the primary foreign key in this case?
I have tried to set the "mappedby" property to null and than just call manager.remove(...), but than there's always another entity-manager that still has a managed instance of the removed entity, so i get the following error:
During synchronization a new object was found through a relationship
that was not marked cascade PERSIST
Below i have put the code of the classes i'm using.
The classOrderBill here has the primary foreign key pointing to OrderWeek. This primary key is also a composite key sadly making the code a bit less readable, but it shouldn't have any effect on the issue i'm having.
public class OrderBill{
#EmbeddedId
private OrderWeekPK orderWeekPK;
#OneToOne
#JoinColumns(value = {
#JoinColumn(name="weekNr", referencedColumnName = "weekNr"),
#JoinColumn(name="yearNr", referencedColumnName = "yearNr")})
#MapsId
private OrderWeek orderWeek;
}
public class OrderWeek{
#OneToOne(mappedBy="orderWeek")
private OrderBill orderBill;
#EmbeddedId
private OrderWeekPK orderWeekPK;
}
#Embeddable
public class OrderWeekPK implements Serializable{
#Column(name="yearNr")
private int yearNr;
#Column(name="weekNr")
private int weekNr;
}
Is there some strategy for this that I am missing?
Update: for now i can circumvent the issue by switching the owning and owned side (transferring the mappedby to the OrderBill class). Now i can break the relationship without touching the foreign primary key.
This "solution" is still less than ideal: OrderWeek now has a redundant column (weekNr2 and yearNr2 for example) that is (or should be) identical to the primary key
Maybe this helps people with the same problem until an actual solution is found.
You could have done away with the #MapsId and embeddable within the OrderBill by using #Id on the orderWeek property and specifying #IdClass(OrderWeekPK.class) on the entity - this might work with your mappings as they are, but isn't the source of the error you mention in your post.
The problem is you have two entities with a bidirectional relationship. If you change the relationship (such as null it out), you are required to merge this change into the context. So to remove an OrderBill instance, you must call em.remove on the instance as well as null out the OrderWeek reference, and call merge on the OrderWeek. This needs to be done in the same transaction, and while the change to OrderWeek's orderBill reference is a no-op in the database, it will keep the cache insync and prevent the OrderBill from being resurrected (or the exceptions you are seeing)
The alternative is to add the orphan removal tag to OrderWeek's orderBill property, which causes JPA to automatically call em.remove if you null out this reference.
I've an entity Course and an entity User. There's a many-to-many relation ship between course and user, since a course can have many users and a user can be enrolled in many courses. In both entities I've put the #ManyToMany annotation on the specific field, that is, in Course I have:
#ManyToMany
private List<RegisteredUser> members;
and in User I have:
#ManyToMany
private List<Course> coursesTaken;
Now, I know that this kind of many-to-many relationships are usually represented by a third table. I also know that there's the annotation #JoinTable which allows us to do that. What I don't know is if I should add this annotation #JoinTable over both fields in the two different entities or not. By the way, if I need to add to both, the names need to match right?
It's actually a good question, and it helps to understand the concept of an "owning" entity because neither side needs a #JoinTable annotation. If you want to prevent both sides from having join tables, a good idea, then you need to have a mappedBy= element on one side. The #JoinTable annotation is used to either specify the table name, or the columns that map the association.
First look at the Javadoc for #JoinTable:
Specifies the mapping of associations. It is applied to the owning side of an association.
Whether or not there is a join table is controlled by the mappedBy="name" element of the #ManyToMany annotation. The Javadoc for mappedBy for the ManyToMany annotation says:
The field that owns the relationship. Required unless the relationship is unidirectional.
For your (bidirectional) example in Hibernate (5.0.9.Final), if there were only two #ManyToMany annotations and no mappedBy= element, the default will have two Entity tables and two Join Tables:
Hibernate: create table Course (id bigint not null, primary key (id))
Hibernate: create table Course_Member (Course_id bigint not null, members_id bigint not null, primary key (Course_id, members_id))
Hibernate: create table Member (id bigint not null, primary key (id))
Hibernate: create table Member_Course (Member_id bigint not null, courses_id bigint not null, primary key (Member_id, courses_id))
While this is saying that each Entity "owns" its ManyToMany relationship, the extra join table is redundant in the typical use case. However, if I decide to have the Member entity "own" the relationship, then I add the mappedBy= element to the Course entity to specify that it doesn't own the relationship:
#ManyToMany(mappedBy="courses")
Set<Member> members;
Adding #JoinTable(name="Member_Course") to the Member entity doesn't change anything: it's only naming the table the same as it would have been named anyway.
Since the Course entity no longer owns its ManyToMany relationship, the extra JoinTable will not be created:
Hibernate: create table Course (id bigint not null, primary key (id))
Hibernate: create table Member (id bigint not null, primary key (id))
Hibernate: create table Member_Course (members_id bigint not null, courses_id bigint not null, primary key (members_id, courses_id))
This is important to the developer because he or she must understand that no relationship is persisted unless it's added to the owning entity, in this case the Member entity. However, since this a bidirectional relationship, the developer should be adding both a Course to Member.courses and a Member to Course.members anyway.
So, if you have a bidirectional ManyToMany relationship, which means you have ManyToMany on both entities involved, then you should add a mappedBy="name" on one of them to avoid having a redundant join table. Since it's bidirectional, I don't think it matters which side you make the owning entity. As always, it's always a good idea to enable the sql logs and see what's going on in the database:
References:
What is the difference between Unidirectional and Bidirectional associations?.
What does relationship owner means in bidirectional relationship?.
What is the “owning side” in an ORM mapping?.
Most efficient way to prevent an infinite recursion in toString()?.
You actually CAN use #JoinTable on both sides and often it makes perfect sense! I am talking out of experience after I had been looking for this solution for weeks.
Even though all throughout the internet, blogs and articles tell a different story - and the Javadoc of JPA is easily misunderstood (or wrong) in this way. I tried it after seeing this uncommented example in a book for professionals - and it worked.
How to do it:
Singer-Instrument-Association:
Singer side:
#ManyToMany
#JoinTable(name = "singer_instrument", joinColumns =
#JoinColumn(name = "SINGER_ID"), inverseJoinColumns = #JoinColumn(name = "INSTRUMENT_ID"))
public Set<Instrument> instruments;
And exactly the same on the other side!
Instrument side:
#ManyToMany
#JoinTable(name = "singer_instrument",
joinColumns = #JoinColumn(name = "INSTRUMENT_ID"),
inverseJoinColumns = #JoinColumn(name = "SINGER_ID"))
public Set<Singer> singers;
So, if you address the same join table, "singer_instrument", with the same name, it work's.
If you address one join table "singer_instrument" and one join table "instrument-singer" though, it will indead result in two different join tables in the database.
This makes a lot of sense, because a many-to-many relationship has no owning side - seen from the database perspective. Owning side means the side, that owns the foreign key of the relationship. But neither the table "singer" nor "instrument" have a foreign key referring to each other. The foreign keys are inside the neccessary join table between them.
The advantage of #JoinTable on both sides of the relation:
Let's say, a singer starts to learn a new instrument: You can add the instrument to singer (and vise versa, as it is bidirectional) and update/merge the singer. The update will update only the singer and the join table. It won't touch the instrument-table.
Now the other case - a guitar-course has ended, so you want to remove the connection between the guitar and the former course-participants/singers: After removing the instrument "guitar" from the singers (and vise versa!), you update/merge the instrument. The update will update only the instrument and the join table. It won't touch the singer-table.
If you had #JoinTable only on one side, you would always have to update/save/delete this side to safely handle the entries in the join table (the relationships between singers and instruments). In this case, you would have to update each singer, who ended the guitar course. That is not reflecting the type of relationship properly and can cause performance issues and conflicts during data transaction.
Nope. Both sides get #ManyToMany, but only one has the #JoinTable
More ManyToMany info here
For #ManyToMany to work on an an existing schema (not made by Hibernate) you will have to use the #JoinTable annotation on both classes to specify the table and which columns map to Java member variables in the appropriate class. I think this example may help you with what properties should be passed to the annotation:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
Imagine 2 tables in a relational database, e.g. Person and Billing. There is a (non-mandatory) OneToOne association defined between these entities, and they share the Person primary key (i.e. PERSON_ID is defined in both Person and Billing, and it is a foreign key in the latter).
When doing a select on Person via a named query such as:
from Person p where p.id = :id
Hibernate/JPA generates two select queries, one on the Person table and another on the Billing table.
The example above is very simple and would not cause any performance issues, given the query returns only one result. Now, imagine that Person has n OneToOne relationships (all non-mandatory) with other entities (all sharing the Person primary key).
Correct me if I'm wrong, but running a select query on Person, returning r rows, would result in (n+1)*r selects being generated by Hibernate, even if the associations are lazy.
Is there a workaround for this potential performance disaster (other than not using a shared primary key at all)? Thank you for all your ideas.
Imagine 2 tables in a relational database, e.g. Person and Billing. There is a (non-mandatory) OneToOne association defined between these entities,
Lazy fetching is conceptually not possible for non-mandatory OneToOne by default, Hibernate has to hit the database to know if the association is null or not. More details from this old wiki page:
Some explanations on lazy loading (one-to-one)
[...]
Now consider our class B has
one-to-one association to C
class B {
private C cee;
public C getCee() {
return cee;
}
public void setCee(C cee) {
this.cee = cee;
}
}
class C {
// Not important really
}
Right after loading B, you may call
getCee() to obtain C. But look,
getCee() is a method of YOUR class
and Hibernate has no control over it.
Hibernate does not know when someone
is going to call getCee(). That
means Hibernate must put an
appropriate value into "cee"
property at the moment it loads B from
database. If proxy is enabled for
C, Hibernate can put a C-proxy
object which is not loaded yet, but
will be loaded when someone uses it.
This gives lazy loading for
one-to-one.
But now imagine your B object may or
may not have associated C
(constrained="false"). What should
getCee() return when specific B
does not have C? Null. But remember,
Hibernate must set correct value of
"cee" at the moment it set B
(because it does no know when someone
will call getCee()). Proxy does not
help here because proxy itself in
already non-null object.
So the resume: if your B->C mapping
is mandatory (constrained=true),
Hibernate will use proxy for C
resulting in lazy initialization. But
if you allow B without C, Hibernate
just HAS TO check presence of C at the
moment it loads B. But a SELECT to
check presence is just inefficient
because the same SELECT may not just
check presence, but load entire
object. So lazy loading goes away.
So, not possible... by default.
Is there a workaround for this potential performance disaster (other than not using a shared primary key at all)? Thank you for all your ideas.
The problem is not the shared primary key, with or without shared primary key, you'll get it, the problem is the nullable OneToOne.
First option: use bytecode instrumentation (see references to the documentation below) and no-proxy fetching:
#OneToOne( fetch = FetchType.LAZY )
#org.hibernate.annotations.LazyToOne(org.hibernate.annotations.LazyToOneOption.NO_PROXY)
Second option: Use a fake ManyToOne(fetch=FetchType.LAZY). That's probably the most simple solution (and to my knowledge, the recommended one). But I didn't test this with a shared PK though.
Third option: Eager load the Billing using a join fetch.
Related question
Making a OneToOne-relation lazy
References
Hibernate Reference Guide
19.1.3. Single-ended association proxies
19.1.7. Using lazy property fetching
Old Hibernate FAQ
How do I set up a 1-to-1 relationship as lazy?
Hibernate Wiki
Some explanations on lazy loading (one-to-one)
This is a common performance issue with Hibernate (just search for "Hibernate n+1"). There are three options to avoiding n+1 queries:
Batch size
Subselect
Do a LEFT JOIN in your query
These are covered in the Hibernate FAQs here and here
Stay away from hibernate's OneToOne mapping
It is very broken and dangerous. You are one minor bug away from a database corruption problem.
http://opensource.atlassian.com/projects/hibernate/browse/HHH-2128
You could try "blind-guess optimization", which is good for "n+1 select problems".
Annotate you field (or getter) like this:
#org.hibernate.annotations.BatchSize(size = 10)
java.util.Set<Billing> bills = new HashSet<Billing>();
That "n+1" problem will only occur if you specify the relationship as as lazy or you explicitly indicate that you want hibernate to run a separate query.
Hibernate can fetch the relationship to Billing with an outer join on the select of Person, obviating the n+1 problem altogether. I think it is the fetch="XXX" indication in your hbm files.
Check out A Short Primer On Fetching Strategies
use optional =true with a one-to-one relationship like this to avoid the n+1 issue
#OneToOne(fetch = FetchType.LAZY, optional=true)
#PrimaryKeyJoinColumn
I have two entities called User and UserProfile in my data model. Here is how they are mapped.
Code from User Entity:
#OneToOne(cascade=CascadeType.ALL)
#PrimaryKeyJoinColumn
public UserProfile getUserProfile(){
return this.userProfile;
}
public void setUserProfile(UserProfile userProfile){
this.userProfile=userProfile;
}
Code from UserProfile Entity:
#OneToOne(mappedBy="userProfile",cascade=CascadeType.ALL)
public User getUser(){
return this.user;
}
public void setUser(User user){
this.user=user;
}
As you see, I have a CascadeType.ALL for the user attribute in UserProfile. But when I try deleting the UserProfile entity, the corresponding User entity still stays. (When I try deleting the User entity, the corresponding UserProfile entity gets deleted.)
Here is my question:-
Do cascades hold only when I specify them on the entity owning the relationship?
Your question is wrong in and of itself, which is where all the confusion stems from. Arthur did a good job with his answer but it's clear from the comments the the confusion still remains so let me take a stab at it here.
Do cascades hold only when I specify
them on the entity owning the
relationship?
"cascade" is an attribute you specify on one (or possibly both in case of bi-directional) end of a relationship. It determines what actions performed on that end will be propagated to the other end. There are many different types of those actions defined in JPA and even more defined in Hibernate extensions. This distinction is important - you should only talk about specific behavior being propagated and not "cascade" in general.
PERSIST, MERGE, REFRESH propagate normally (from the end they were declared on to the other).
REMOVE, however, is tricky because it can mean two different things. If you have a relationship between A and B and you're trying to remove A, you can either remove B on the other end OR you can remove the association but leave B intact.
Hibernate makes a clear distinction between the two - you can declare REMOVE (DELETE) and DELETE_ORPHAN cascade types separately; JPA spec does not. Note that DELETE_ORPHAN is not supported to single-valued relationships (OneToOne / ManyToOne).
Thus, propagation of REMOVE (by itself or when it's part of ALL) depends on whether relationship has a clear owner (uni-directional always does; bi-directional does if it's mapped using mappedBy and does not if it's mapped via join table) in which case it's propagated from owner to owned OR no owner in which case it's propagated in either direction but without DELETE_ORPHAN semantics unless it was explicitly specified. Typical example of the latter is bi-directional many-to-many.
As said
When i try deleting the UserProfile entity, the corresponding User entity still stays
Maybe when you try to remove a UserProfile you get an integrity constraint violation from the database - do you use MyISAM engine in MySQL ?
But as you does not says nothing about it. Maybe your UserProfile entity does not have a reference to a User entity.
As said in JPA specification
remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is annotated with the cascade=REMOVE or cascade=ALL annotation element value
Something like
UserProfile up = entityManager.find(UserProfile.class, id);
entityManager.close();
// Notice User is null outside a persistence context
// So user will be not removed from the database because UserProfile does not have a reference to it
up.setUser(null);
entityManager.getTransaction().begin();
entityManager.remove(up);
entityManager.getTransaction().commit();
Or you have something like
entityManager.getTransaction().begin();
UserProfile up = entityManager.find(UserProfile.class, id);
// throws UPDATE USER_PROFILE SET USER_ID = NULL
up.setUser(null);
// up.getUser() is null
// So user is not removed
entityManager.remove(up);
entityManager.getTransaction().commit();
In response to ChhsPly's comment:
In Java Persistence with Hibernate book, you see the following
The cascade attribute is directional: It applies to only one end of the association.
I think it would be better as
It applies to only one end of the association per operation
So you can put cascade attribute in both sides at the same time, even in a bidirectional relationship. So ChssPly is right.
mappdeBy attribute sets up the bidirectional relationship. mappedBy attribute designated the Address entity as the inverse side of the relationship. This means that the Customer entity is the owning side of the relationship.
ChssPly is right when he says mappedBy has nothing to do with cascading
That is correct when you have a bi-directional relationship the owner dictates the cascade rules since it is the "owner". The "owned" entity essentially follows orders, it can't give the orders -- so to speak.
With JPA 2.x , if you want a cascade remove then use orphanRemoval attribute :
#OneToMany(orphanRemoval=true)
check documentation here for more info.