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.
Related
When using Optimistic Locking with Hibernate; is it possible to allow adding of children in a ManyToMany parent child relationship without increasing the version number of the parent without using #OptimisticLock(excluded = true) on the parents side?
There are two entities in this case; Parent and Child. There is a ManyToMany relationship between them, using a JoinTable.
class Parent {
// ... some code...
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "children")
#OptimisticLock(excluded = true) // Is it possible to get rid of this?
public List<Child> getChildren() {
return this.children;
}
public void setChildren(List<Child> children) {
this.children = children;
}
}
class Child {
// ... some code...
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "PARENT_CHILD", joinColumns = {#JoinColumn(name = "child_id")}, inverseJoinColumns = {#JoinColumn(name = "parent_id")})
public List<Parent> getParents() {
return this.parents;
}
public void setParents(List<Parent> parents) {
this.parents = parents;
}
}
Without the #OptimisticLock(excluded = true) annotation the version number of the parent seems to be still increased, when a child is added, even though the owning side of the relationship is on the side of the children - this version number increment leads to an OptimisticLockException in the end.
The goal is to be able to add several children in concurrently without getting an OptimisticLockException. This can be achieved by adding the #OptimisticLock(excluded = true) annotation to the parent side of the relationship. Is it possible to make this work without this extra annotation?
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");
}
I have a situation when using hibernate that my child gets persisted no matter what in a one-to-one mapping.
Say I have a parent entity,
#Entity
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Parent {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(unique = true)
private Child child;
// getter/setter ...
}
Child entity,
#Entity
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Child {
#OneToOne(mappedBy = "child")
private Parent parent;
// getter/setter ...
}
When saving I always have to,
public class ParentServiceImpl ... {
public void save(ParentDTO parentDTO) {
// Map parent DTO to parent DAO
....
// Check if child is not null
if (parentDAO.getChild() != null) {
childRepository.save(parentDAO.getChild());
}
}
}
Without having to check with getChild whether child is null or not how can I achieve the persistence in hibernate for a parent with a nullable child ? In order words say I have a parent with no associated child then I want to only create a row for the parent and not a row for the child with nullable columns.
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.
I have a legacy database, which I am using EJB3 to model. The database is in quite a poor shape, and we have certain unusual restrictions on how we insert into the DB. Now I want to model the database in a hierarchy that fits in with the DB strucuture, but I want to be able to manually insert each entity individually without the persistence manager trying to persist the entities children.
I am trying something like the following (boilerplate left out):
#Entity
#Table(name = "PARENT_TABLE")
public class Parent {
#Id
#Column(name = "ID")
int id;
#OneToMany
List<Child> children;
}
#Entity
#Table(name = "CHILD_TABLE")
public class Child {
#Id
#Column(name = "ID")
int id;
}
Now this throws an exception:
java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST
Now I know the entity isn't marked PERSIST - I don't want the EntityManager to persist it! I want to be able to persist the parent first, and then the child - but not together. There are good reasons for wanting to do it this way, but it doesn't seem to want to play.
Heh welcome to the hair-pulling that is JPA configuration.
In your case you have two choices:
Manually persist the new object; or
Automatically persist it.
To automatically persist it you need to annotate the relationship. This is a common one-to-many idiom:
#Entity
#Table(name = "PARENT_TABLE")
public class Parent {
#Id private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private Collection<Child> children;
public void addChild(Child child) {
if (children == null) {
children = new ArrayList<Child>();
}
child.setParent(parent);
children.add(child);
}
}
#Entity
#Table(name = "CHILD_TABLE")
public class Child {
#Id private Long id;
#ManyToOne
private Parent parent;
public void setParent(Parnet parent) {
this.parent = parent;
}
}
Parent parent = // build or load parent
Child child = // build child
parent.addChild(child);
Because of the cascade persist this will work.
Note: You have to manage the relationship at a Java level yourself, hence manually setting the parent. This is important.
Without it you need to manually persist the object. You'll need an EntityManager to do that, in which case it is as simple as:
entityManager.persist(child);
At which point it will work correctly (assuming everything else does).
For purely child entities I would favour the annotation approach. It's just easier.
There is one gotcha I'll mention with JPA:
Parent parent = new Parent();
entityManager.persist(parent);
Child child = new Child();
parent.addChild(child);
Now I'm a little rusty on this but I believe that you may run into problems if you do this because the parent was persisted before the child was added. Be careful and check this case no matter what you do.