I am using the Hibernate persistence API. I have two entities, you can check the Parent of these: (The Child entity does not contain any reference to the Parent - because it is not necessary business logically.)
#Entity(...)
public class ParentEntity implements Serializable {
#Id
private Integer id;
// ...
#OneToMany(fetch = FetchType.LAZY)
#org.hibernate.annotations.Cascade({ org.hibernate.annotations.CascadeType.ALL }) // losing JPA's portability
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private Set<ChildEntity> children;
// ...
}
When I persist a new Parent entity, a DB trigger is executed. This trigger inserts some Children by default values. But after persisting, the children are not in the collection. How can I solve this? Here is my Dao method:
#Transactional(readOnly = false)
#Override
public Parent addParent(String name) {
Parent temp = new Parent(name);
entityManager.persist(temp);
return entityManager.find(Parent.class, temp.getId()); // return temp;
}
Thank you in advance very much for everything.
Parent temp = new Parent(name);
entityManager.persist(temp);
If i get you properly,
You are creating a new parent object (without any child entity) and persisting it.
So a parent object without any child is persisted in DB.
When you do
entityManager.find(Parent.class, temp.getId());
You can't expect to have a parent class with child entities.
Since you are persisting the child object by a db trigger, there must be some way to put parent_id (tem.getId()/parent.getId()) in them
There is no other to way to find child objects of the persisted parent.
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 have created a program by using JPA and SpringBoot, the database is Postgresql, i have two entities: Parent and Child:
#Entity
#Table(name = "parent")
public class Parent {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private Set<Child> children = new HashSet<>();
}
And the Child entity:
#Entity
#Table(name = "child")
public class Child {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name = "parent")
private Parent parent;
}
Then in the Application, i have autowired two repositories to do some tests:
It works when i do:
Child child1 = new Child("Lucas", new Date(2012, 12,12));
Parent parent1 = new Parent("Jack", "Bauer");
child1.setParent(parent1);
childRepository.save(child1);
In the table Child, the parent id is set correctly.
But if i create from another side, it doesn't work:
Child child1 = new Child("Lucas", new Date(2012, 12,12));
Parent parent1 = new Parent("Jack", "Bauer");
childRepository.save(child1);
parent1.getChildren().add(child1);
parentRepository.save(parent1);
No error appears, and no relationship is updated in the table Child
Can you tell me why?
Thank you.
Bidirectional #OneToMany:
The best way to map a #OneToMany association is to rely on the #ManyToOne side to propagate all entity state changes:
Parent Class:
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Child> childs = new ArrayList<>();
//Constructors, getters and setters removed for brevity
public void addChild(Child child) {
childs.add(child);
comment.setChild(this);
}
public void removeChild(Child child) {
childs.remove(child);
child.setPost(null);
}
Child Class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Parent parent;
The #ManyToOne association uses FetchType.LAZY because, otherwise, we’d fall back to EAGER fetching which is bad for performance
The parent entity, features two utility methods (e.g. addChild and removeChild) which are used to synchronize both sides of the bidirectional association. You should always provide these methods whenever you are working with a bidirectional association as, otherwise, you risk very subtle state propagation issues.
For test :
Parent parent1=new Parent();
// set datas into parent1 and to put childs we can use the utility method addChild
parent1.addChild(new Child(datas...))
parent1.addChild(new Child(datas...)) //etc
parentRepository.save(parent1);
The question you have is why does the Cascade operation fail to work when you add a Child to the Parent and have a cascade annotation on the Parent.
Generally the owner of the relationship, in this case the Child as indicated by the mappedBy="parent" annotation, is responsible for persisting the relation. You have demonstrated this with the unidirectional mapping for the Child -- done with the ManyToOne annotation.
Child child = new Child();
Parent parent = new Parent();
child.setParent(parent);
parentRepo.save(parent);
childRepo.save(child);
You then you tried the same thing with the bidirectional mapping in the Parent -- done with the OneToMany annotation. Since this annotation includes the mappedBy="parent" annotation it is not the owner and normally anything added to the Set<Child> children would be ignored. However you added the cascade = CascadeType.ALL annotation so this overrides the ownership settings and allows the Parent entity to persist relations for a subset of operations and specific conditions as determined by the CascadeType value.
But how is the parent to know which children to persist? I assume that it looks at whether the child instance has already been persisted. If it has, then no cascade operation would be needed. When you persisted the child instance yourself you circumvented the cascade operation.
Child child = new Child();
Parent parent = new Parent();
Set<Child> children = new HashSet<>();
childRepo.save(child);
children.add(child);
parent.setChildren(children);
parentRepo.save(parent);
This particular code give me an error because the child instance has been saved and detached and then asked to be saved again. The error condition doesn't always happen - I think depending on whether the parent is new or has been retrieved from the db.
org.hibernate.PersistentObjectException: detached entity passed to persist:
So if you want the Parent entity to do a cascade you have to pass it a Child instance that has not been already saved. Note that you still have to set the child's parent in order for the relation to be created otherwise the parent will persist a parentless child.
Child child = new Child();
Parent parent = new Parent();
child.setParent(parent);
Set<Child> children = new HashSet<>();
children.add(child);
parent.setChildren(children);
parentRepo.saveAndFlush(parent);
And this works fine for me. Note that I create the Set of children myself instead of creating it every time a Parent entity is instantiated. Generally you will be doing queries against a database much more often then updates and for every query the JPA provider will put its own Collection class into the children property of the Parent and so the set you instantiated will generally end up on the garbage heap -- somewhat inefficient.
#Entity
public class Child {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Parent parent;
#Entity
public class Parent {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval=true)
private Set<Child> children;
I've searched the SO and didn't find an appropriate solution for this. Say I have a parent entity:
#Entity
public class Parent {
#Id
#GeneratedValue
private int id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private List<Child> childList;
}
And a child entity, which have foreign key associated with the parent entity:
#Entity
public class Child {
#Id
#GeneratedValue
private int id;
#JoinColumn(name = "parentId")
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Parent parent;
}
My scenario is a little bit special. The child entity is generated in a huge quantity every hour and I would need to save them into the database. Those child objects may have same parents, and the associate parent entity may already exists in the database. My requirement is to save all these child objects without query out the managed parent entity from entityManager, and if the parent entity exists, just merge/update the existed one. Such as:
Child c = new Child();
// set some properties of child
Parent p = new Parent();
// set some properties from my data into the parent. The parent may already exists
child.setParent(p);
JpaRepo.saveAndFlush(child);// If parent already exists, merge with the existed parent object, otherwise, create new parent object.
Apparently this doesn't work. When parent entity doesn't exist, it will correctly create the parent entity and associated with it. But if the parent already exists, it will throw exception about duplicate key, and if I set the Id of the parent (use dummy value to force make it go through merge), it will throw exception of detached entity object.
Due to the performance constraint, I can't load the parent entity from database as there's too many of them. So is there any way to automatically for JPA or database to merge the object when primary key violate?
I'm using MySQL, is that possible to use ON DUPLICATE KEY UPDATE?
It is a bit tricky staff you would like to achieve. If lazy loading etc is not an option (which I prefer) I recommend to You create an another entity:
#Entity
#Table(name = "sameAsTheOriginal")
public class ChildSaveObject {
#Id
#GeneratedValue
private int id; //I think int will run out fast if there are a lot of objects but it is your choice. (Int max value: 2 147 483 647 is not that mutch) I prefer Long az id
#Column(name = "parentId")
private int parent; //note that You need to have the id of the parent object
//Other properties...
}
In this flow you have to check if the parent object exists and use the ChildSaveObject to save your additional child to it.
I do not really like one to many mappings in most of the cases, I think they are just roots of a lot of problems. Still if you prefer this way, my recommendation is to use fetch = FetchType.LAZY and get the parent entity (this way it will not load all the childs you do not need).
Here are my entities:
#Entity
public class Parent {
#Id
#Column(name = "ID")
private Long id;
#OneToMany(cascade = {CascadeType.ALL}, mappedBy = "parent")
private Set<Child> childs = new HashSet<Child>();
...
}
The child:
#Entity
public class Child {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="PARENTID", nullable = false)
private Parent parent;
...
}
I want to perform following operations:
Delete child entity from parent (not the parent itself).
Add new child entity to parent (parent.setChild(child)).
Now save the child entity in to DB and update parent accordingly.
This is what I tried but it raises ConstraintViolationexception for parent:
entityManager.remove(parent.getChild())
parent.setChild(new Child())
entityManager.merge(parent);
How can I fix this?
The 'old' child probably still references the parent, while the new child does not. Both is an issue.
In addition to removing an old child, you should set the parent reference of the child instance to null.
In addition to adding the new child to the parent, you will need to add the parent to the child in order to provide the foreign key.
Do not cascade from the many side (child) to the one side (parent). The behavior for this type of cascades is undefined and might work in an unexpected way.
EDIT: what the JPA 2.0 spec has to say:
Note that it is the application that bears responsibility for maintaining
the consistency of runtime relationships—for example, for
insuring that the “one” and the “many” sides of a bidirectional
relationship are consistent with one another when the application
updates the relationship at runtime.
Modify the relation in the parent as follows:
#OneToMany(cascade = {CascadeType.ALL}, orphanRemoval=true, mappedBy = "parent")
Just set the new child to the parent and merge the parent. Now the children referencing earlier becomes orphans and JPA automatically deletes those while committing the transaction.
Thanks,
JK
I have a one-to-many relation between Parent and Child table. In the parent object I have a
List<Child> setChildren(List<Child> childs)
I also have a foreign key in the Child table. This foreign key is an ID that references a Parent row in database. So in my database configuration this foreign key can not be NULL.
Also this foreign key is the primary key in the Parent table.
So my question is how I can automatically save the children objects by doing something like this:
session.save(parent);
I tried the above but I'm getting a database error complaining that the foreign key field in the Child table can not be NULL. Is there a way to tell JPA to automatically set this foreign key into the Child object so it can automatically save children objects?
I tried the above but I'm getting a database error complaining that the foreign key field in the Child table can not be NULL. Is there a way to tell JPA to automatically set this foreign key into the Child object so it can automatically save children objects?
Well, there are two things here.
First, you need to cascade the save operation (but my understanding is that you are doing this or you wouldn't get a FK constraint violation during inserts in the "child" table)
Second, you probably have a bidirectional association and I think that you're not setting "both sides of the link" correctly. You are supposed to do something like this:
Parent parent = new Parent();
...
Child c1 = new Child();
...
c1.setParent(parent);
List<Child> children = new ArrayList<Child>();
children.add(c1);
parent.setChildren(children);
session.save(parent);
A common pattern is to use link management methods:
#Entity
public class Parent {
#Id private Long id;
#OneToMany(mappedBy="parent")
private List<Child> children = new ArrayList<Child>();
...
protected void setChildren(List<Child> children) {
this.children = children;
}
public void addToChildren(Child child) {
child.setParent(this);
this.children.add(child);
}
}
And the code becomes:
Parent parent = new Parent();
...
Child c1 = new Child();
...
parent.addToChildren(c1);
session.save(parent);
References
Hibernate Core Reference Guide
1.2.6. Working bi-directional links
I believe you need to set the cascade option in your mapping via xml/annotation. Refer to Hibernate reference example here.
In case you are using annotation, you need to do something like this,
#OneToMany(cascade = CascadeType.PERSIST) // Other options are CascadeType.ALL, CascadeType.UPDATE etc..
Following program describe how bidirectional relation work in hibernate.
When parent will save its list of child object will be auto save.
On Parent side:
#Entity
#Table(name="clients")
public class Clients implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#OneToMany(mappedBy="clients", cascade=CascadeType.ALL)
List<SmsNumbers> smsNumbers;
}
And put the following annotation on the child side:
#Entity
#Table(name="smsnumbers")
public class SmsNumbers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
int id;
String number;
String status;
Date reg_date;
#ManyToOne
#JoinColumn(name = "client_id")
private Clients clients;
// and getter setter.
}
Main class:
public static void main(String arr[])
{
Session session = HibernateUtil.openSession();
//getting transaction object from session object
session.beginTransaction();
Clients cl=new Clients("Murali", "1010101010");
SmsNumbers sms1=new SmsNumbers("99999", "Active", cl);
SmsNumbers sms2=new SmsNumbers("88888", "InActive", cl);
SmsNumbers sms3=new SmsNumbers("77777", "Active", cl);
List<SmsNumbers> lstSmsNumbers=new ArrayList<SmsNumbers>();
lstSmsNumbers.add(sms1);
lstSmsNumbers.add(sms2);
lstSmsNumbers.add(sms3);
cl.setSmsNumbers(lstSmsNumbers);
session.saveOrUpdate(cl);
session.getTransaction().commit();
session.close();
}
in your setChilds, you might want to try looping thru the list and doing something like
child.parent = this;
you also should set up the cascade on the parent to the appropriate values.
Here are the ways to assign parent object in child object of Bi-directional relations ?
Suppose you have a relation say One-To-Many,then for each parent object,a set of child object exists.
In bi-directional relations,each child object will have reference to its parent.
eg : Each Department will have list of Employees and each Employee is part of some department.This is called Bi directional relations.
To achieve this, one way is to assign parent in child object while persisting parent object
Parent parent = new Parent();
...
Child c1 = new Child();
...
c1.setParent(parent);
List<Child> children = new ArrayList<Child>();
children.add(c1);
parent.setChilds(children);
session.save(parent);
Other way is, you can do using hibernate Intercepter,this way helps you not to write above code for all models.
Hibernate interceptor provide apis to do your own work before perform any DB operation.Likewise onSave of object, we can assign parent object in child objects using reflection.
public class CustomEntityInterceptor extends EmptyInterceptor {
#Override
public boolean onSave(
final Object entity, final Serializable id, final Object[] state, final String[] propertyNames,
final Type[] types) {
if (types != null) {
for (int i = 0; i < types.length; i++) {
if (types[i].isCollectionType()) {
String propertyName = propertyNames[i];
propertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
try {
Method method = entity.getClass().getMethod("get" + propertyName);
List<Object> objectList = (List<Object>) method.invoke(entity);
if (objectList != null) {
for (Object object : objectList) {
String entityName = entity.getClass().getSimpleName();
Method eachMethod = object.getClass().getMethod("set" + entityName, entity.getClass());
eachMethod.invoke(object, entity);
}
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
return true;
}
}
And you can register Intercepter to configuration as
new Configuration().setInterceptor( new CustomEntityInterceptor() );
In JPA #*To* relationships both parent and child entities must be cross assigned before (parent) saving.
Use org.hibernate.annotations for doing Cascade , if the hibernate and JPA are used together , its somehow complaining on saving the child objects.
In short set cascade type to all , will do a job;
For an example in your model.
Add Code like this .
#OneToMany(mappedBy = "receipt", cascade=CascadeType.ALL)
private List saleSet;
If you do not have bidirectional relationship and want to only save/update the the single column in the child table, then you can create JPA repository with child Entity and call save/saveAll or update method.
Note: if you come across FK violations then it means your postman request having primary and foreign key ids is not matching with generated ids in child table , check the ids in your request and child table which your are going to update(they should match/if they don't means you get FK violations) whatever ids generated while saving the parent and child in before transactions, those ids should match in your second call when you try to update the single column in your child table.
Parent:
#Entity
#Table(name="Customer")
public class Customer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private UUID customerId ;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name ="child_columnName", referencedColumnName=
"parent_columnName")
List<Accounts> accountList;
}
Child :
#Entity
#Table(name="Account")
public class Account implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private UUID accountid;
}