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;
}
Related
I have a project built with spring boot 2.2.6 and I am trying to insert/update an entry(Parent) in a database and cascade de operation to its children, if a child doesn’t exist it should be inserted otherwise it should be updated. I have the following classes:
#Entity
#Data
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
List<Child> children1;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
List<Child> children2;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(of = {"id"})
public class Child {
#Id
private Long id;
private String name;
}
public interface ParentRepository extends JpaRepository<Parent, Long> {
}
#Service
public class TestService {
private ParentRepository parentRepository;
public TestService(ParentRepository parentRepository) {
this.parentRepository = parentRepository;
}
#Transactional
public void save() {
Parent parent = new Parent();
Child c1 = new Child(1L, "c1");
Child c2 = new Child(1L, "c1");
List<Child> children1 = new ArrayList<>();
children1.add(c1);
List<Child> children2 = new ArrayList<>();
children1.add(c2);
parent.setChildren1(children1);
parent.setChildren2(children2);
parentRepository.save(parent);
}
}
I don’t have any other configuration and with this I receive the following error:
A different object with the same identifier value was already associated with the session : [Child#1]
I don’t understand why I am receiving this error because c1.equals(c2) is evaluated to true.
If I change the type of children1 and children2 to Set, I am receiving the following error:
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.CHILD(ID) [1, 'c1']"
Now I don’t understand why I am receiving this error because I am expecting the merge cascade type to handle this issue, to make updates instead of inserts.
Can you help me understand what am I missing out? Can you give me some tips on what is the best practice to insert/update objects in such a situation?
Although the objects equal in Java, it doesn't mean they are the same entity in the persistence context. Once you do:
Child c1 = new Child(1L, "c1");
Child c2 = new Child(1L, "c1");
... and persist them using the PERSIST cascade, both objects are about to be persisted and since their IDs are equal, the unique ID constraint is violated. The solution is to use the same object, therefore the entity will be created only once:
Child c = new Child(1L, "c1");
List<Child> children = new ArrayList<>();
children.add(c);
parent.setChildren1(children);
parent.setChildren2(children);
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'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).
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.
Updated: I wound up "solving" the problem by doing the opposite! I now have the entity reference field set as read-only (insertable=false updatable=false), and the foreign key field read-write. This means I need to take special care when saving new entities, but on querying, the entity properties get resolved for me.
I have a bidirectional one-to-many association in my domain model, where I'm using JPA annotations and Hibernate as the persistence provider. It's pretty much your bog-standard parent/child configuration, with one difference being that I want to expose the parent's foreign key as a separate property of the child alongside the reference to a parent instance, like so:
#Entity
public class Child {
#Id #GeneratedValue
Long id;
#Column(name="parent_id", insertable=false, updatable=false)
private Long parentId;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="parent_id")
private Parent parent;
private long timestamp;
}
#Entity
public class Parent {
#Id #GeneratedValue
Long id;
#OrderBy("timestamp")
#OneToMany(mappedBy="parent", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Child> children;
}
This works just fine most of the time, but there are many (legacy) cases when I'd like to put an invalid value in the parent_id column without having to create a bogus Parent first.
Unfortunately, Hibernate won't save values assigned to the parentId field due to insertable=false, updatable=false, which it requires when the same column is mapped to multiple properties. Is there any nice way to "go behind Hibernate's back" and sneak values into that field without having to drop down to JDBC or implement an interceptor?
Thanks!
Whats wrong about a bogus Parent? There is a neat way to do it in one place:
public void setParent(Parent parent) {
this.parent = parent;
this.parentId = parent == null ? null : parent.getId();
}
public void setParentId(Long parentId) {
final Parent parent;
if (parentId == null) {
parent = null;
} else {
parent = new Parent();
parent.setId(parentId);
}
setParent(parent);
}
Have you looked into setting the ManyToOne on the child inverse=true, instead of telling the property value to be un-insertable/updatable? If the inverse=true does what it used to, it'll make the Child entity "not the source of truth" for the relationship.. It'll still read the column, but not write it.. I think. It's been a while since I've been in this situation.