Cascading in JPA+ Hibernate - java

Bellow are my entities:
public class EntityA {
//...
#OneToMany(mappedBy="entityA")
private Set entitieBs;
}
public class EntityB {
//...
#ManyToOne(cascade=CascadeType.PERSIST)
private EntityA entityA;
}
with accessors methods(getters and setters).
I intend that every time when I save a new EntityB object in the database (with an EntityA object set up as the "parent"), if I call EntityA.getEntityBs() on the parent of new EntityB, to have it added in the result Set. But if I do it as in my example it doesn't work.
Does anybody know where I am wrong?
Thanks!
Here is my java code how I persist the entity:
//...some code
EntityB eb = new EntityB();
eb.setEntityA(entityA);
entityManager.persist(entityB);
I want to make clear that I don't add entityB to entityA's set of entityBs.

This question is asked every two days.
JPA doesn't maintain the coherence of the object graph for you. It's your responsibility to maintain both sides of a bidirectional association. Everything will be as you expect if you commit the transaction, close the session, and reload the entities, because you have initialized the owning side of the association. But if you modify one side of the association in memory, JPA won't modify the other side for you.

Related

Hibernate trying to save a detached entity - LazyInitializationException

I have the following entities:
#Entity
public class B{
#OneToMany
private List<C> cList;
private Long d;
}
In my managed bean, I need to load a specific b (which is perfectly working) in order to edit the contained attributes (cList, d):
#ManagedBean
public class Bean{
private B b;
public void onEvent(Long bId){
b = bManager.load(bId);
}
}
The attributes of B will have to be edited using a JSF-Form. I do not want these changes to be reflected to the database.
The problem is pretty much the same like in this (old) thread.
But none of the suggestions worked in my case (tried em.flush(), em.detach(), session.evict()).
Is there no solution except deep copying?
It is possible to do it, but first would be better to know what hibernate is doing and why you are getting exception. Here is documentation about object states
If you want to access list with objects C or you want to modify some of them, you must fetch it before it gets to your managed bean. By default hibernate is fetching objects lazy and associated objects will be loaded when you access them, but preconditions is to have a transaction and session attached to objects. So in your managed bean objects are detached and list of C cannot be fetch at that time. To solve that problem you must fetch all object that you want to change before they gets to the managed bean. i.e.
#OneToMany
#Fetch(FetchMode.JOIN) // load it with sql join
private List<C> cList;
There many other ways you can achieve same result. So now you can update your B and list of C
entities and then call update function for your B entity.
Hope it helps.

Explicit delete on JPA relationships

I am a bit confused about managing relationship in JPA.
basically I have two entities with a One to Many relationship
A configuration can have have a one or many email list associated with it.
#Entity
public class Config {
#OneToMany(mappedBy="owner",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
private List<Email> emailReceivers;
}
#Entity
public class Email {
#ManyToOne
private Config owner;
}
In an EJB and during update/merge operation wherein I would edit the list of emails associated with a configuration,
I thought that I dont need to explicitly call the delete operation on my email Entity and I would just manage the relationship by deleting the email in my configuration email list.
#Stateless
public class ConfigFacadeImpl implements ConfigFacade{
#EJB
private ConfigDao configDao;
#EJB
private EmailDao emailDao;
#Override
public void update(Config Config, List<Email> emailsForDelete) {
if(emailsForDelete!=null && emailsForDelete.size() > 0){
for(Email emailTemp: emailsForDelete){
Email email = emailDao.find(emailTemp.getId());
emailDao.delete(email); // Do I need to explicitly call the remove??
config.getEmailReceivers().remove(email);
}
}
configDao.update(config);
}
}
If I don't execute the delete and only remove it from the list, it wont erase my table row.
The UI and the database is now not in sync as the UI would not show the email(s) that I have deleted but when you check the database, the row(s) are still there.
Is it required? I thought JPA would handle this for me if I would just remove it in my entities.
UPDATE
I have tweaked my code to get the entity from the database first before making any changes but still it is not deleting my child email entities. I wonder if this is an apache derby issues. (This is the correct way right as I am passing my entities from my JSF managed bean into my EJB so I need to get the sync from the DB first.)
#Override
public void update(Config config, List<Email> emailsForDelete) {
Config configTemp = configDao.find(config.getId());
if(emailsForDelete!=null && emailsForDelete.size() > 0){
for(Email emailTemp: emailsForDelete){
configTemp.getEmailReceivers().remove(emailTemp);
}
}
configDao.update(config);
}
Since you have already defined cascade type = CascadeType.ALL, JPA should take care of the deletion. Explicit Delete statement is not required.
These two statements are not required:
Email email = emailDao.find(emailTemp.getId());
emailDao.delete(email); // Do I need to explicitly call the remove??
Instead, you may want to just find the matching emailReceiver in config.getEmailReceivers() and remove the matching EmailReceivers as you are doing. There is no need to load the Email entity from the database.
EDIT: To delete orphan objects, you may want to include CascadeType.DELETE_ORPHAN cascade attribute along with CascadeType.ALL.
This is the same issue as in Why merging is not cascaded on a one to many relationship
Basically, JPA can only cascade over entities in your collection. So changes to child objects removed from the collection are never putinto the context, and so can't be pushed to the database. In this case, the oneToMany is controlled by the manytones back pointer, so even collection changes won't show up unless the child is also merged. Once a child is pruned from the tree, it needs to be managed and merged individually for changes to it to be picked up.
With JPA 2.0, you can use the option orphanRemoval=true in parent entity
Example:
#Entity
public class Parent {
...
#OneToMany(mappedBy="parentId",cascade=CascadeType.ALL, orphanRemoval=true)
private List<Child> childList;
...
}

JPA query returns proxied entities

Suppose I have 2 entities, EntityA and EntityB.
EntityB is #OneToOne related to EntityA:
#Entity
public class EntityB {
#OneToOne(fetch = FetchType.LAZY)
private EntityA entA;
// other stuff
}
When I load EntityB from DB, the corresponding EntityA (say entA1) is lazy loaded.
After that I load EntityA list by
List result = entityManager.createQuery("select A from EntityA A")
.setFirstResult(start).setMaxResults(end).getResultList();
The result list contains both previously lazy loaded and proxied EntityA and normal materialized EntityAs such like:
EntityA
EntityA_$$_javassist_nnn <--- entA1 which is loaded lazily earlier
EntityA
...
So my questions:
1) Is this an expected behavior? Where can I find apidoc info about that?
2) Can I entirely load proxied entities only or entirely load eagerly all of them? Not mixed.
Yes, it's expected behavior. Hibernate does everything it can to have one and only one instance of an entity in the session. Since it already has a proxy to EntityA, stored in the session when you loaded EntityB, a subsequent query returning the same EntityA instance effectively return the same instance: the proxy already stored in the session.
You shouldn't care much about the fact that the list contains proxies. Calling any method on the proxy (except getClass()) will return the same thing as calling it on the unproxied entity.
AFAIK, that's what allows having collections of entities behaving correctly with attached objects, although the objects don't even have an equals() method.

Hibernate not caching my OneToOne relationship on the inverse side

I have code like:
#Entity
#Table(name = "A")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class A
{
#OneToOne(cascade={CascadeType.ALL}, fetch=FetchType.EAGER, mappedBy="a")
public B getB() {};
}
#Entity
#Table(name = "B")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class B
{
#OneToOne(cascade={}, fetch=FetchType.LAZY)
#JoinColumn(name="A_ID")
public A getA() {};
}
each time when A is loaded there is query for B. Why is A.getB() not cached after A is loaded and is it possible to cache it?
Workaround that work for me is create additional method with #OneToMany
#OneToMany(cascade={}, fetch=FetchType.EAGER, mappedBy="a")
public Set<B> getBSet() {};
#Transient
public B getB() { return b.iterator().next(); }
I'm not very happy with this solutions, but it works and I can't find other way.
Try putting #Cache annotation on getB() getter as well. My observations are that if you cache the object, it's associations may not be considered cached.
It may be a little more work, but you could try making the fetchType Lazy, and
do the fetching of B explicitly. That way you could check whether the instance of B has already been loaded or not?
On a side note, have you seen this post? I think the problem is similar:
https://forum.hibernate.org/viewtopic.php?p=2378461
I feel that the original answer does not cover entirly why this is happening.
Why OneToOne is not cached ?
It is not cached because class A is not the owner of the relationship and does not contain the #JoinColumn inside its table. Therefore there is no way for class A to tell what is the ID of class B. This is why when trying to retrieve class A it needs to send a query for class B to figure out what the ID of class B is, but when it sends the query the class B is already loaded so there is no need for it to actualy retrieve it from the cache.
When OneToOne will be cached ?
Now if you navigate the opposite way from class B to class A then you will hit the cache straight away :)
Why is #OneToMany(cascade={}, fetch=FetchType.EAGER, mappedBy="a") working ?
In hibernate collections are cached in their dedicated region known as collection cache. Hibernate caches the primary keys of the entities that make up the collection. Not the entities themselves; i.e. there is no Set stored somewhere in the second level cache.
Once the primary key for is retrieved from the collection cache region it falls back to the regular entity cache to retrieve the actual object. Therefore the #OneToMany hack works for you.

JPA: question on impedance mismatch in OneToMany relations

I have a question about JPA-2.0 (provider is Hibernate) relationships and their corresponding management in Java. Let's assume i have a Department and an Employee entity:
#Entity
public class Department {
...
#OneToMany(mappedBy = "department")
private Set<Employee> employees = new HashSet<Employee>();
...
}
#Entity
public class Employee {
...
#ManyToOne(targetEntity = Department.class)
#JoinColumn
private Department department;
...
}
Now i know i have to manage the Java relationships myself, as in the following unit test:
#Transactional
#Test
public void testBoth() {
Department d = new Department();
Employee e = new Employee();
e.setDepartment(d);
d.getEmployees().add(e);
em.persist(d);
em.persist(e);
assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
assertNotNull(em.find(Department.class, d.getId()).getEmployees());
}
If i leave out either e.setDepartment(d) or d.getEmployees().add(e) the assertions will fail. So far, so good. What if i commit the database transaction in between?
#Test
public void testBoth() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Department d = new Department();
Employee e = new Employee();
e.setDepartment(d);
d.getEmployees().add(e);
em.persist(d);
em.persist(e);
em.getTransaction().commit();
em.close();
em = emf.createEntityManager();
em.getTransaction().begin();
assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
assertNotNull(em.find(Department.class, d.getId()).getEmployees());
em.getTransaction().commit();
em.close();
}
Do i still need to manage both sides of the relation? No, as it turns out, i don't have to. With this modification
e.setDepartment(d);
//d.getEmployees().add(e);
the assertions still succeed. However, if i only set the other side:
//e.setDepartment(d);
d.getEmployees().add(e);
the assertions fail. Why? Is is because the Employee is the owning side of the relation? Can i change that behavior by annotating differently? Or is it just always the "One" side of the "OneToMany" that determines when the foreign key field in the database is filled?
I don't know what your test is trying to demonstrate but the fact is you must handle both sides of the association when working with bidirectional associations. Not doing so is incorrect. Period.
Update: While the spec reference mentioned by axtavt is of course accurate, I insist, you definitely must set both sides of a bi-directional association. Not doing so is incorrect and the association between your entities in the first persistence context is broken. The JPA wiki book puts it like this:
As with all bi-directional relationships it is your object model's and application's responsibility to maintain the relationship in both direction. There is no magic in JPA, if you add or remove to one side of the collection, you must also add or remove from the other side, see object corruption. Technically the database will be updated correctly if you only add/remove from the owning side of the relationship, but then your object model will be out of synch, which can cause issues.
In other words, the only correct and safe way to manage your bidirectional association in Java is to set both sides of the link. This is usually done using defensive link management methods, like this:
#Entity
public class Department {
...
#OneToMany(mappedBy = "department")
private Set<Employee> employees = new HashSet<Employee>();
...
public void addToEmployees(Employee employee) {
this.employees.add(employee);
employee.setDepartment(this);
}
}
I repeat, not doing so is incorrect. Your test only works because you're hitting the database in a new persistence context (i.e. a very particular situation, not the general one) but the code would break in many other situations.
Entity relationships in JPA have owning and inverse sides. Database updates are determined by the state of the owning side. In your case Employee is an owning side due to the mappedBy attribute.
From the JPA 2.0 specification:
2.9 Entity Relationships
...
Relationships may be bidirectional or
unidirectional. A bidirectional
relationship has both an owning side
and an inverse (non-owning) side. A
unidirectional relationship has only
an owning side. The owning side of a
relationship determines the updates to
the relationship in the database, as
described in section
3.2.4.
The following rules apply to bidirectional relationships:
The inverse side of a bidirectional
relationship must refer to its owning
side by use of the mappedBy element of
the OneToOne, OneToMany, or ManyToMany
annotation. The mappedBy element
designates the property or field in
the entity that is the owner of the
relationship.
The many side of
one-to-many / many-to-one
bidirectional relationships must be
the owning side, hence the mappedBy
element cannot be specified on the
ManyToOne annotation.
For
one-to-one bidirectional
relationships, the owning side
corresponds to the side that contains
the corresponding foreign key.
For
many-to-many bidirectional
relationships either side may be the
owning side.
The reason why the second test in a new persistence context succeeds if you only update the owning side in a previous context is that the persistence provider obviously can't know that when persisting you did not update the inverse side as well. It only cares about the owning side for persistence purposes. However, when you get persistent objects from a persistence provider, the provider sets the bidirectional associations properly on both sides (it is simply assumed they were persisted properly, too). However, as many others here have already pointed out, it is not the responsibility of the persistence provider to complete newly created bidirectional associations and you should always properly maintain bidirectional associations in your code.

Categories