JPA2 Lazy Child Collection with orphanRemoval=true deletes 'not-fetched' children - java

I have a Parent entity that owns(via mappedBy) a FetchType.LAZY Set<Child> with orphanDelete=true.
A client can happily add and remove child rows via the parent's collection getter and their changes are properly committed after an em.merge(parent).
However, if a client merges a parent WITHOUT accessing the child collection, all child rows are deleted on the parent's commit.
The same behavior is exhibited under OpenJPA 2.1.0 and the 2.1.1-20110610.205956-18 snapshot binary.
Any pointers would be appreciated.
An example to illustrate:
#Entity
public class Parent{
#Column
private String name;
#OneToMany(mappedBy="parent", fetch=LAZY, cascade = ALL, orphanRemoval=true)
private Set<Child> children;
public String getName(){return name;}
public void setName(String name){this.name=name;}
public Set<Child> getChildren(){
return children;
}
}
#Entity
public class Child{
#ManyToOne(optional = false, targetEntity = Parent.class)
#JoinColumn(name="parent_id", nullable=false)
private Parent parent;
}
Both entities have #Id and #Version properties declared and implement appropriate hashCode, equals and compareTo methods.
The following client code works perfectly, the parent.name is updated, 1 child is inserted and 1 child is deleted
EntityTransaction eTx=em.getTransaction();
eTx.begin();
Parent par=em.find(Parent.class, parId);
//PersistenceUnitUtil.isLoaded(par) returns true
//PersistenceUnitUtil.isLoaded(par, "children") returns false
Collection<Child> children=par.getChildren();
//PersistenceUnitUtil.isLoaded(par, "children") returns true
Child child = children.iterator().next();
par.getChildren().remove(child);
par.getChildren().add(new Child(par, "I'm New"));
par.setName("I am Updated");
par=em.merge(par);
eTx.commit();
The following code will issue delete commands for each of the parent's children:
EntityTransaction eTx=em.getTransaction();
eTx.begin();
Parent par=em.find(Parent.class, parId);
//PersistenceUnitUtil.isLoaded(par) here returns true, and
//PersistenceUnitUtil.isLoaded(par, "children") returns false
par.setName("I am Updated");
par=em.merge(par);
eTx.commit();

Just don't call merge. There's no need for it. Within the scope of a transaction, changes made to an object that was looked up within that transaction will be persisted unless the object is detached.

Related

Hibernate: #OneToMany: delete entity from "Many" side causes EntityNotFoundException

I have the following entities:
#Entity
public static class Parent {
#Id
#GeneratedValue
private Long id;
String st;
#OneToMany(mappedBy = "parent")
Set<Child> children = new HashSet<>();
// get,set
}
#Entity
public static class Child {
#Id
#GeneratedValue
private Long id;
String st;
#ManyToOne()
private Parent parent;
//get,set
}
Note, that there is no Cascade on #OneToMany side.
And I want the following:
I have one Parent with one Child in Detached state.
Now I want to remove child by some condition, so I'm accesing all children, find necessary and remove it directly via em.remove(child). + I remove it from Parent's collection.
After that I want to change some property of Parent and save it also.
And I'm getting EntityNotFound exception.
I performed some debug, and found that children collection is PersistentSet which remembered it's state in storedSnapshot. So, when I'm merging Parent to context - Hibernate do something with that stored snapshot and tries to load child it from DB. Of course, there is no such entity and exception is thrown.
So, there are couple of things I could do:
Map collection with #NotFound(action = NotFoundAction.IGNORE)
During removing from children collection - cast to PersistentSet and clear it also.
But it seems like a hack.
So,
1. What I'm doing wrong? It seems, that it's correct to remove child entity directly
2. Is there more elegant way to handle this?
Reproducible example:
#Autowired
PrentCrud parentDao;
#Autowired
ChiildCrud childDao;
#PostConstruct
public void doSomething() {
LogManager.getLogger("org.hibernate.SQL").setLevel(Level.DEBUG);
Parent p = new Parent();
p.setSt("1");
Child e = new Child();
e.setParent(p);
e.setSt("c");
p.getChildren().add(e);
Parent save = parentDao.save(p);
e.setParent(save);
childDao.save(e);
Parent next = parentDao.findAll().iterator().next();
next.setSt("2");
next.getChildren().size();
childDao.deleteAll();
next.getChildren().clear();
if (next.getChildren() instanceof PersistentSet) { // this is hack, not working without
((Map)((PersistentSet) next.getChildren()).getStoredSnapshot()).clear();
}
parentDao.save(next); // exception is thrwn here without hack
System.out.println("Success");
}
have you tried changing fetch type to eager? defaults for relations
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
maybe it gets cached because of fetch method
You can use next.setChildren(new HashSet<>()); instead of next.getChildren().clear(); to get rid of the getStoredSnapshot()).clear()
But it would be more elegant to use cascade and orphanRemoval.
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
Set<Child> children = new HashSet<>();
public void doSomething() {
...
next.setSt("2");
next.setChildren(new HashSet<>());
parentDao.save(next);
System.out.println("Success");
}

Delete child from parent and parent from child automatically with JPA annotations

Suppose that we have 3 Entities object class:
class Parent {
String name;
List<Child> children;
}
class Child {
String name;
Parent parent;
}
class Toy {
String name;
Child child;
}
How can I use JPA2.x (or hibernate) annotations to:
Delete all children automatically when parent delete (one to many)
Delete child automatically from children list when it is deleted (many to one)
Delete toy automatically when child remove (one to one)
I'm using Hibernate 4.3.5 and mysql 5.1.30.
Thanks
The remove entity state transition should cascade from parent to children, not the other way around.
You need something like this:
class Parent {
String name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
List<Child> children = new ArrayList<>();
public void addChild(Child child) {
child.setParent(this);
children.add(child);
}
public void removeChild(Child child) {
children.remove(child);
child.setParent(null);
}
}
class Child {
String name;
#ManyToOne
Parent parent;
#OneToOne(mappedBy = "child", cascade = CascadeType.ALL, orphanRemoval = true)
Toy toy;
}
class Toy {
String name;
#OneToOne
Child child;
}
You should use CascadeType.REMOVE. This is common annotation for both Hibernate and JPA. Hibernate has another similar type CacadeType like CascadeType.DELETE.
Delete all children automatically when parent delete (one to many)
class Parent {
String name;
#OneToMany(cascade = CascadeType.REMOVE)
List<Child> children;
}
Delete child automatically from children list when it is deleted (many to one)
class Child {
String name;
#ManyToOne(cascade = CascadeType.REMOVE)
Parent parent;
}
Delete toy automatically when child remove (one to one)
class Toy {
String name;
#OneToOne(cascade = CascadeType.REMOVE)
Child child;
}
orphanRemoval is delete all orphan entity example: store (s) has books(b1,b2,b3) and b1 has title(t) in this case if deleted store(s) some books(b2,b3) will be deleted. B2 and t still exist. if you use "cascade= CascadeType.Remove" just store(s) and all books will be deleted (only "t" exist).
s->b1,b2,b3 b2->t ------after(orphanRemoval = true)--------- b2->t
s->b1,b2,b3 b2->t ------ after(cascade=CascadeType.REMOVE)--------- t
If orphanRemoval=true is specified the disconnected entity instance is automatically removed. This is useful for cleaning up dependent objects that should not exist without a reference from an owner object.
If only cascade=CascadeType.REMOVE is specified no automatic action is taken since disconnecting a relationship is not a remove operation.

Hibernate: cascade deletion of a child collection

Suppose I have 2 Java objects: Parent and Child.
The relationship between them is Child -> Parent = many-to-one, i.e. a number of Child objects can be associated with the same Parent.
A Child object is holding a reference to its Parent meanwhile Parent object has no field to address its Children.
In Hibernate that results in having a many-to-one element in Child mapping; Parent's mapping doesn't contain one-to-many entry since there is no need for a Parent object to have a collection field to reference all its Child objects.
Now, when Parent is deleted Oracle throws an exception that the entity cannot be deleted while there are child entities referencing it.
With this object model, is there a way to casacadely delete all Child objects that belong to Parent object when the latter gets deleted?
1> make bi directional mapping to Child <--> parent. mention cascade delete.
2> you know who are the childs object of this parent, first delete all childs of this parent and then try to delete parent.
EDIT:
check this example link
Hibernate Bidirectional Example
Class Parent
import java.util.Set;
import javax.persistence.*;
#Entity
#Table(name = "Parent")
public class Parent{
#Id
#GeneratedValue
private Integer id;
private String name;
#OneToMany(mappedBy="Child", cascade=CascadeType.ALL)
private Set<Child> child;
}
consider second class called child
import javax.persistence.*;
#Entity
#Table(name = "Child")
public class Child{
#Id
#GeneratedValue
private Integer id;
private String lastname;
#ManyToOne
#JoinColumn(name = "id")
private Parent parent;
}
just make changes in your POJO's accordingly it will not throw exception

Hibernate unidirectional OneToMany delete violates constraint ( optional=false at parent side?)

I use Hibernate 3.6 and I have something like this:
#Entity
public class Parent {
#OnyToMany( fetch = FetchType.LAZY, cascade = { ascadeType.ALL } )
#Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE )
#JoinColumn( name="Parent_ID" )
public List<Child> getChildren() { return children; }
public void setChildren( List<Child> children ) { this.children = children; }
private transient List<TitleMetadataCategory> children;
...
}
#Entity
public class Child {
....
}
Association is unidirectional for several reasons and I don't want to change it . In addition orphan children don't exist, so there is DB constraint that CHILD.PARENT_ID is not null.
All works fine, except removing child. When I do
parent.getChildren().remove(child);
session.saveOrUpdate(parent).
it fails.
Since I don't have
#ManyToOne( optional=false )
at the child side Hibernate tries to update child with PARENT_ID=NULL and fails due to DB constraint.
Is there any way to fix it?
Have you tried
#JoinColumn(name = "Parent_ID", nullable = false)
?
Also, note that attached entities are automatically persistent. You don't need to call saveOrUpdate().
The answer of JB Nizet is working, but with one correction. Since I also have Child.getParentId() method ( not getParent() ), its Column annotation should have nullable=false, insertable=false, updateble=false parameters in addition to nullable=false, updatable=false in Parent.getChildren() association.
With the current configuration Hibernate doesn't know that the Child has to be deleted when you remove it from children collection (it's called orphan removal). You need #OneToMany(orphanRemoval=true) in the parent. org.hibernate.annotations.CascadeType.DELETE only specifies that child should be deleted too when the entire parent is deleted.

JPA #OneToMany -> Parent - Child Reference (Foreign Key)

i have a Question about referencing ParentEntities from Child Entites ir
If i have something like this:
Parent.java:
#Entity(name ="Parent")
public class Parent {
#Id
#Generate.....
#Column
private int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent")
private Set<Child> children;
simple ... getter and setter ...
}
And the Child.java:
#Entity(name ="Child")
public class Child{
#Id
#Generate....
#Column
private int id;
#ManyToOne
private Parent parent;
... simple getter an setter
}
Following Tables are going to be created:
Parent:
int id
Child:
int id
int parent_id (foreign key: parent.id)
Ok, so far, everthings fine. But when it comes to using this Reference from Java, i would think, you can do something like this.
#Transactional
public void test() {
Parent parent = new Parent();
Child child = new Child();
Set<Child> children = new HashSet<Child>();
children.add(child);
parent.setChildren(children);
entityManager.persist(parent);
}
which leads to this in Database:
Parent:
id
100
Child
id paren_id
101 100
But thats not the case, you have to explicity set the Parent to the Child (which, i would think, the framework could probably do by itself).
So whats really in the database is this:
Parent:
id
100
Child
id paren_id
101 (null)
cause i haven't set the Parent to the Child. So my Question:
Do I really have to do sth. like this?
Parent.java:
...
setChildren(Set<Child> children) {
for (Child child : children) {
child.setParent.(this);
}
this.children = children;
}
...
Edit:
According to the fast Replies i was able to solve this Problem by using the #JoinColumn on the Reference-Owning Entity. If we take the Example from above, i did sth. like this:
Parent.java:
#Entity(name ="Parent")
public class Parent {
#Id
#Generate.....
#Column
private int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name= "paren_id")
private Set<Child> children;
simple ... getter and setter ...
}
And the Child.java:
#Entity(name ="Child")
public class Child{
#Id
#Generate....
#Column
private int id;
... simple getter an setter
}
Now if we do this:
#Transactional
public void test() {
Parent parent = new Parent();
Child child = new Child();
Set<Child> children = new HashSet<Child>();
children.add(child);
parent.setChildren(children);
entityManager.persist(parent);
}
The Reference is correctly set by the Parent:
Parent:
id
100
Child
id paren_id
101 100
Do I really have to do sth. like this?
That is one strategy, yes.
On bi-directional relationships there is an "owning" and a "non-owning" side of the relationship. Because the owning side in your case is on Child, you need to set the relationship there for it to be persisted. The owning side is usually determined by where you specify #JoinColumn, but it doesn't look like you're using that annotation, so it's likely being inferred from the fact that you used mappedBy in the Parent annotation.
You can read a lot more about this here.
It still seems to be the case. In parent Entity you can have something like
#PrePersist
private void prePersist() {
children.forEach( c -> c.setParent(this));
}
in order to avoid repeating code for setting child/parent relationship elsewhere in code.
Yes, that is the case. JPA does not keep care about consistency of your entity graph. Especially you have to set it to the owner side of bidirectional relationship (in your case to the parent attribute of Child).
In JPA 2.0 specification this is said with following words:
Note that it is the application that bears responsibility for
maintaining the consistency of run- time relationships—for example,
for insuring that the “one” and the “many” sides of a bidi- rectional
relationship are consistent with one another when the application
updates the relationship at runtime.
We ran into a problem while persisting a simple object graph like the one shown above. Running in H2 everything would work, but when we ran against MySQL the "paren_id" in the child table (defined in the #JoinColumn annotation) wasn't getting populated with the generated id of the parent - even though it was set as a non-null column with a foreign key constraint in the DB.
We'd get an exception like this:
org.hibernate.exception.GenericJDBCException: Field 'paren_id' doesn't have a default value
For anyone else who might run into this, what we eventually found was that we had to another attribute to the #JoinColumn to get it to work:
#JoinColumn(name="paren_id", nullable=false)
If I am getting you correctly, according to EntityManager, if you want it to manage the transaction's insert order your have to "tell him" that it should persist the children too. And you are not doing that, so "he" doesn't know what to persist, but your parent's child list is not empty so "he" takes it has correct but the stored value is null.
So you should consider do something like:
... begin, etc
em.persist(child)
em.persist(parent)
do what you want with the parent object here then commit and this should work for similar cases too.

Categories