I have 3 types of objects - Parent, Child, and ChildAttr
My goal is to transfer a subset of Children from one Parent to another using Hibernate (3.2.5).
Objects are structured as such:
public class Parent {
Set<Child> children;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "parent")
#Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
public Set<Child> getChildren() {
return this.children;
}
public void setChildren(Set<Child> children) {
this.children = children;
}
}
...
public class Child {
Set<ChildAttr> attributes;
Parent parent;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "child")
#Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
public Set<ChildAttr> getAttributes() {
return this.attributes;
}
public void setAttributes(Set<ChildAttr> attributes) {
this.attributes = attributes;
}
}
...
public class ChildAttr {
Child child;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "child_id", nullable = false)
public Child getChild() {
return this.child;
}
public void setChild(Child child) {
this.child = child;
}
}
Now say I run some client code that takes a subset of Parent A's Child objects and moves them to Parent B:
Set<Child> children = getChildrenToTransfer(transferCriteria, parentA);
parentA.getChildren().removeAll(children);
manager.saveOrUpdate(parentA); // method also calls flush();
parentB.getChildren().addAll(children);
manager.saveOrUpdate(parentB); // error thrown here.
I get an error thrown when trying to save parentB.
Found two representations of same collection: com.mycode.Child.attributes;
The application currently seems to do this work fine in multiple sessions - e.g. - some user comes along and removes the set of children, then some time later adds them to some other Parent. Moreover, I don't really understand why it's instantiating multiple versions of that attribute list when it should really just be one, even if the Parent changes.
What is causing the aforementioned error and how do I get around it?
One rule of thumb: ensure the objects are consistent as an object graph before trying to persist them... iow you're removing all the children from parent A and are adding them to parentB but you have not updated the parent link for those children.
So I'd suggest the following:
add methods to Parent:
add(Child child) {
child.setParent0(this);
children.add(child);
}
remove(Child child) {
child.setParent0(null);
children.remove(child);
}
And then on Child:
setParent0(Parent parent) {
this.parent = parent;
}
setParent(Parent parent) {
parent.add(this);
}
This way when you add from either direction you've got a consistent object model without the outside code knowing the details.
It makes more sense to remove from the parent...
So try with these methods.
Seens thats because your bidirectional relation (parent-children). When you remove/add the child to one parent to another, you should update the parent reference.
Related
I have checked different sources but none solve my problem, such as:
https://coderanch.com/t/671882/databases/Updating-child-DTO-object-MapsId
Spring + Hibernate : a different object with the same identifier value was already associated with the session
My case: I have created 2 classes, 1 repository as below:
#Entity
public class Parent{
#Id
public long pid;
public String name;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
public List<Child> children;
}
-------------------------------------------------------------------
#Entity
public class Child{
#EmbeddedId
public PK childPK = new PK();
public String name;
#ManyToOne
#MapsId("parentPk")
#JoinColumn(name = "foreignKeyFromParent")
public Parent parent;
#Embeddable
#EqualsAndHashCode
static class PK implements Serializable {
public long parentPk;
public long cid;
}
}
------------------------------------------------------------------------
public interface ParentRepository extends JpaRepository<AmazonTest, Long> {
}
Where Parent and Child has One To Many relationship.
In my main method:
public static void main(String[] args) {
#Autowired
private ParentRepository parentRepository;
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.childPK.cid = 1;
child.name = "Child 1";
childList.add(child);
parent.children= childList;
parentRepository.save(parent);
parentRepository.flush();
}
When I run the application for the first time, data can successfully saved to the database. But if I run it again, it gives error "Exception: org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session".
I was expecting if the data is new, it will update my database, if data is the same, nothing happen. What's wrong with my code.
If I made parent stand alone (without any relationship with the child). It will not give any error even I rerun the application.
Edited: However, if I use the below implementation with simple primary key in Child Entity, it will work as I expected. I can rerun the application without error. I can also change the value, such as the child.name and it will reflect in database.
#Entity
public class Parent{
#Id
public long pid;
public String name;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
public List<Child> children;
}
-------------------------------------------------------------------
#Entity
public class Child{
#Id
public long cid;
public String name;
#ManyToOne
#JoinColumn(name = "foreignKeyFromParent")
public Parent parent;
}
------------------------------------------------------------------------
public interface ParentRepository extends JpaRepository<AmazonTest, Long> {
}
-------------------------------------------------------------------------
public static void main(String[] args) {
#Autowired
private ParentRepository parentRepository;
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.cid = 1;
child.name = "Child 1";
childList.add(child);
parent.children= childList;
parentRepository.save(parent);
parentRepository.flush();
}
Well, parent.pid is your database primary key. You can only save one recordset to the database with id=1. This is expected behaviour.
Maybe make yourself familiar with #GeneratedValue in order to avoid setting the id yourself.
Before full explaination a little note: try to post code that actually compiles and works as advertised.
Your main() does not compile,
you dont set up full relation between Parent and Child.
Also try to explicitely demarcate transactions in the posted example.
How your code works
You are calling save on a repository. Underneath, this method calls entityManager.merge() as you have set an id yourself. Merge calls SQL Select to verify if the object is there, and subsequently calls SQL insert or update for the object. (The suggestions that save with the object with id that exists in db are wrong)
In the first run, the object is not there.
you insert parent
merge is cascaded and you insert child (lets call it childA)
In the second run
merge selects parent (with childA)
We compare if new parent is already in the session.
This is done in SessionImpl.getEntityUsingInterceptor
parent is found
merge is cascaded to the child
again, we check if the object is already in the session.
Now the difference comes:
Depending on how you set up the relation between child and parent, the child may have an incomplete PK (and rely on filling it from the relation to parent annotated with #MapsId). Unfortunately, the entity is not found in the session via the incomplete PK, but later, when saving, the PK is complete, and now, you have 2 confilicting objects with the same key.
To solve it
Child child = new Child();
child.parent = parent;
child.childPK.cid = 1;
child.childPK.parentPk = 1;
This also explains why the code works when you change the PK of Child to a long - there is no way to screw it up and have an incomplete PK.
NOTE
The solution above makes mess with orphans.
I still think that the original solution is better as the orphans are removed.
Also, adding updated soution to original solution is a worthwhile update.
Removing entire list and re-inserting it is not likely perform well under load.
Unfortunalely it removes the list on the first merge of the parent, and re-adds them on the second merge of the parent. (This is why clear is not needed)
Better still, just find the parent entity and make the updates on it (as other answers suggest).
Even better, try to look at the solution and add / replace only specific children of the parent, not lookig at the parent and its children ollection. This will be likely most performant.
Original Solution
I propose the following (note that total replacement of the chilren list is not allowed, as it is a hibernate proxy).
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Child> children = new ArrayList<>();
#SpringBootTest
public class ParentOrphanRepositoryTest {
#Autowired
private ParentOrphanRepository parentOrphanRepository;
#Test
public void testDoubleAdd() {
addEntity();
addEntity();
}
#Transactional
public void addEntity() {
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
parent = parentOrphanRepository.save(parent);
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.parent = parent;
child.childPK.cid = 1;
child.name = "Child 1";
childList.add(child);
// parent.children.clear(); Not needed.
parent.children.addAll(childList);
parentOrphanRepository.save(parent);
parentOrphanRepository.flush();
}
}
I use Spring Data Jpa and Hibernate is the provider.
I have a Parent class mapped as follows:
#Entity
#Table(name="parent")
public class Parent {
private List<Child> childs;
private List<AnotherChild> anotherChilds;
#OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
public List<Child> getChilds() {
return childs;
}
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
public List<AnotherChild> getAntoherChilds() {
return anotherChilds;
}
}
and child:
#Entity
#Table(name="child")
public class Child {
private Parent parent;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "column_name")
public Parent getParent() {
return patern;
}
}
#Entity
#Table(name="another_child")
public class AnotherChild {
private Parent parent;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "column_name")
public Parent getParent() {
return patern;
}
}
When I load the parent from the database, it doesn't load the list of
child immediately and
When I call parent.getChilds(), it returns
null.
Can you give some advice? Am I wrong anywhere? Thanks.
EDIT:
After some research, I realize that when I have only single child, it loaded eagerly (like it should). But when I have multiple child, it doesn't - even though it has been marked FetchType.EAGER and the other FetchType.LAZY.
Note: If I marked both as FetchType.EAGER, it'll throws MultipleBagFetchException: cannot simultaneously fetch multiple bags.
The same happened when I annotate it using #Fetch(FetchMode.JOIN)
If added Entity annotation parent.getChilds() should not come empty.it would be better as you do Entity.
#Entity
#Table(name="PARENT_TBL")
public class Parent {
//other fields
#OneToMany(mappedBy = "parent",fetch = FetchType.LAZY,cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE},orphanRemoval = true)
private List<Child> childs;
//getter setter
}
#Entity
#Table(name="CHILD_TBL")
public class Child {
//other fields
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
private Parent parent;
//getter setter
}
Example Get Parent Query;
public Parent getParent(long parentId) throws Exception {
session = sessionFactory.openSession();
Criteria cr = session.createCriteria(Parent.class, "parent");
cr.setFetchMode('parent.childs', FetchMode.JOIN);
cr.add( Restrictions.eq("parent.id", parentId));
Parent parent = cr.uniqueResult();
tx = session.getTransaction();
session.beginTransaction();
tx.commit();
return parent;
EAGER loading of collections means that they are fetched fully at the time their parent is fetched. So if you have Parent and it has List, all the childs are fetched from the database at the time the Parent is fetched.
LAZY on the other hand means that the contents of the List are fetched only when you try to access them. For example, by calling parent.getChilds().iterator(). Calling any access method on the List will initiate a call to the database to retrieve the elements. This is implemented by creating a Proxy around the List (or Set). So for your lazy collections, the concrete types are not ArrayList and HashSet.
I am trying to keep the collections of the entities up to date with the internal database structure but failing to do so with a bidirectional, cascade-delete relation between Parent and Child.
Deleting a parent should cascade-delete all children
Addition and deletion of a child should be reflected in the parent's getChildren() set
The code below works if there is only one child, any more than that and I get ConcurrentModificationException, which is logical since Hibernate iterates over the collection when cascading.
If I remove the #PreRemove the removeChild test below fails.
Any suggestions on how to solve this without adding a specific deleteChild method that performs the clean up? I am trying to avoid having any clean-up methods outside of the entities.
#Entity
public class Parent {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.REMOVE)
private Set<Child> children = new HashSet<>();
public Set<Child> getChildren() {
return Collections.unmodifiableSet(children);
}
void internalAddChild(final Child child) {
children.add(child);
}
void internalRemoveChild(final Child child) {
children.remove(child);
}
}
#Entity
public class Child {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", nullable = false)
private Parent parent;
public Child(final Parent parent) {
setParent(parent);
}
public final void setParent(final Parent parent) {
if (this.parent != null) {
this.parent.internalRemoveChild(this);
}
this.parent = parent;
if (parent != null) {
parent.internalAddChild(this);
}
}
#PreRemove
private void preRemove() {
// Causes ConcurrentModificationException in test removeParent below
if (parent != null) {
parent.internalRemoveChild(this);
}
}
}
Tests:
#Test
public void removeParent() {
EntityManager em = getEntityManager()
Parent parent = new Parent();
em.persist(parent);
em.persist(new Child(parent));
em.persist(new Child(parent));
assertTrue(parent.getChildren().size() == 2);
// Causes ConcurrentModificationException if more than 1 child
em.remove(parent);
// Both children should be deleted
}
#Test
public void removeChild() {
EntityManager em = getEntityManager()
Parent parent = new Parent();
em.persist(parent);
Child child = new Child(parent);
em.persist(child);
em.remove(child);
// Fails without #PreRemove in Child, child is still present in set
assertFalse(parent.getChildren().contains(child));
}
Exception stack trace:
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:789)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:379)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:319)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:296)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:118)
at org.hibernate.event.internal.DefaultDeleteEventListener.cascadeBeforeDelete(DefaultDeleteEventListener.java:353)
at org.hibernate.event.internal.DefaultDeleteEventListener.deleteEntity(DefaultDeleteEventListener.java:275)
at org.hibernate.event.internal.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:160)
at org.hibernate.event.internal.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireDelete(SessionImpl.java:920)
at org.hibernate.internal.SessionImpl.delete(SessionImpl.java:896)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.remove(AbstractEntityManagerImpl.java:1214)
...
Try putting orphanRemoval = true on #OneToMany mapping, and remove CascadeType.REMOVE since it is now redundant. This instructs the persistence provider to remove child entities when parent is deleted, or their relation is set to null.
One side note (may affect this problem but doesn't have to, it's just good practice) is to avoid wiring up the relations in the constructor (like you do now in Child, and instead move the logic to some kind of addChild and removeChild methods (internalRemoveChild and internalAddChild in your case). It would look like this
void internalAddChild(final Child child) {
if (child != null) {
child.setParent(this);
children.add(child);
}
}
void internalRemoveChild(final Child child) {
if (child != null) {
children.remove(child);
child.setParent(null);
}
}
// test code
Parent parent = new Parent();
Child c1 = new Child();
Child c2 = new Child();
parent.internalAddChild(c1);
parent.internalAddChild(c2);
em.persist(parent);
em.persist(c1);
em.persist(c2);
I have two entities:
parent :
public class UserGroup{
#OneToMany(fetch = FetchType.EAGER, mappedBy = "userGroup", cascade = CascadeType.ALL)
private List<User> users;
}
and child:
public class User{
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinColumn(name = "user_group_id")
private UserGroup userGroup;
}
when i am trying to save UserGroup with one User in list of users, with this method:
#Transactional
public E save(E e) {
em.persist(e);
em.flush();
em.refresh(e);
return e;
}
my parent and child is getting saved, but user_group_id in child object is null.
Is there any solution?
By considering you are giving UserGroup object along with list of User to save method: so your code should be:
em.save(userGroup);
for(User user : UserGroup.getUsers())
{
user.setuser_group_id(userGroup.getUserGroupId());
em.save(user);
}
You have a bidirectional relationship. The correct way of saving it is putting both references in the entities.
You should do:
userGroup.getUsers().add(user);
user.setUserGroup(userGroup);
entityManager.persist(userGroup);
In your parent setter method of child, do this.
public void setChildren(Collection<Child> children) {
this.children = children;
for(Child child: this.children) {
child.setParent(this)
}
}
This should solve.
I'm trying to find a way to manage in a lightweight fashion two models that have a OneToMany relationship but will never be persisted.
My search has lead me to the javax.persistence that seems to be able to do what i want but i do not seem to be able to make it work.
As tests always say much more than a speech, here's what i'd like to achieve:
public void test_relationship() {
Parent p = new Parent("Mary");
Child c1 = new Child("Hugo");
Child c2 = new Child("Charly");
Child c3 = new Child("Françine");
p.addChild(c1)
Assert.assertEquals(p, c1.getParent());
p.removeChild(c1)
Assert.assertNull(c1.getParent());
p.addChildren(c1, c2)
Assert.assertEquals(p, c1.getParent());
Assert.assertEquals(p, c2.getParent());
c1.removeParent();
Assert.assertFalse(p.hasChild(c1));
c1.setParent(p);
Assert.assertTrue(p.hasChild(c1));
}
All this without a database. It's only purpose is to facilitate the access from one object to another. These objects will be built depending on data received as an HTTP request payload (JSON format) and will need to be serialized back to JSON as part of the response.
For now here's what i've done
#Entity
public class Parent implements Serializable {
private Collection<Child> children = new HashSet<Child>();
#Id
private String id;
#OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
public Collection<Child> getChildren() {
return children;
}
public void addChild(child) {
this.children.add(child);
}
public void removeChild(child) {
this.children.remove(child);
}
public boolean hasChild(child) {
this.children.contains(child);
}
}
#Entity
public class Child implements Serializable {
private Parent parent;
#Id
private String id;
#ManyToOne
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
public void removeParent() {
this.parent = null;
}
}
Edit:
I'm expecting the relationship behavior to come from javax.persistence. Maybe through an EntityManager ? I'm really confused on how those work. I know I could code this behavior myself but I would like to avoid to do this on each relation of each of my entities and have the driest code possible.
You are part of the way there. The bit that is missing is that when you create/update/remove one side of a Parent <-> Child relationship you need to do the same on the other side.
So for example
public void setParent(Parent parent) {
if (this.parent != null) {
this.parent.removeChild(this);
}
this.parent = parent;
parent.addChild(this);
}
(And change childs to children or the grammar monster will eat you!)