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).
Related
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.
This has already been asked a number of times, but I don't find any good answers so I'll ask it again.
I have parent-children unidirectional relation as follows:
#Entity
#Table(name = "PARENT")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long parentId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinTable(name = "CHILD", joinColumns = #JoinColumn(name = "parent_id"), inverseJoinColumns = #JoinColumn(name = "ID"))
private List<Child> children;
}
#Entity
#Table(name = "CHILD")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "PARENT_ID")
private Long parentId;
//some other field
}
I create an instance of the parent, assign a list of children to it and try to persist it:
Parent p = new Parent();
List<Child> children = new ArrayList<Child>();
Child c = new Child();
children.add(c);
p.addChildren(children);
em.merge(p);
When the code runs, I get the following exception:
MySQLIntegrityConstraintViolationException: Cannot add or update a
child row: a foreign key constraint fails
(testdb.child, CONSTRAINT parent_fk
FOREIGN KEY (parent_id) REFERENCES parent (id) ON
DELETE NO ACTION ON UPDATE NO ACTION)
I'm assuming this is due to the fact that the parent is not fully inserted when the child is being attempted to be inserted.
If I don't add the children to the parent, the parent gets inserted just fine.
I tried switching the GeneratedValue generation strategy but that did not help.
Any ideas how to insert the parent & the children at the same time?
Edit: Even if I persist the parent first, I'm still getting the same error. I determined it's because the parent_id is not set in the child; the child is default constructed and thus the parent_id is set to 0 which does not exist thus the foreign key constraint validation.
Is there a way to get jpa to automatically set the parent_id of the children that are assigned to the parent instance?
Your relationship does not have to be bi-directional. There is some mis-information in the comments here.
You also said that you had added the field "parentId" into the Child entity because you assumed that JPA needs to "know" about the parent field so that it can set the value. The problem is not that JPA does not know about the field, based on the annotations that you have provided. The problem is that you have provided "too much" information about the field, but that information is not internally consistent.
Change your field and annotation in Parent to:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn(name = "parent_id")
private List<Child> children;
Then remove the "parentId" from the Child entity entirely.
You had previously specified a JoinTable annotation. However, what you want is not a JoinTable. A JoinTable would create an additional third table in order to relate the two entities to each other. What you want is only a JoinColumn. Once you add the JoinColumn annotation onto a field that is also annotated with OneToMany, your JPA implementation will know that you are adding a FK into the CHILD table. The problem is that JPA has a CHILD table already defined with a column parent_id.
Think of it that you are giving it two conflicting definitions of both the function of the CHILD table and the parent_id column. In one case, you have told you JPA that it is an entity and the parent_id is simply a value in that entity. In the other, you have told JPA that your CHILD table is not an entity, but is used to create a foreign key relationship between your CHILD and PARENT table. The problem is that your CHILD table already exists. Then when you are persisting your entity, you have told it that the parent_id is explicitly null (not set) but then you have also told it that your parent_id should be updated to set a foreign key reference to the parent table.
I modified your code with the changes I described above, and I also called "persist" instead of "merge".
This resulted in 3 SQL queries
insert into PARENT (ID) values (default)
insert into CHILD (ID) values (default)
update CHILD set parent_id=? where ID=?
This reflects what you want perfectly. The PARENT entry is created. The CHILD entry is created, and then the CHILD record is updated to correctly set the foreign key.
If you instead add the annotation
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn(name = "parent_id", nullable = false)
private List<Child> children;
Then it will run the following query when it inserts the child
insert into CHILD (ID, parent_id) values (default, ?)
thus setting your FK propertly from the very beginning.
Adding updatable=false to the parent entity solved the problem with both an insert and an updated being executed on the child table. However, I have no clue why that's the case and in fact, I don't think what I am doing is correct because it means I cannot update the child table later on if I have to.
I know persisting a new parent with children works for me using em.persists(...).
Using em.merge(...), really I don't know, but it sounds like it should work, but obviously you are running into troubles as your JPA implementation is trying to persists children before parent.
Maybe check if this works for you : https://vnageswararao.wordpress.com/2011/10/06/persist-entities-with-parent-child-relationship-using-jpa/
I don't know if this plays a role in your problem, but keep in mind that em.merge(p); will return a managed entity... and p will remain un-managed, and your children are linked to p.
A) try em.persists(...) rather than em.merge(...)
if you can't
B) you are merging parent... and you cascade is set to CascadeType.PERSIST. Try changing it to
cascade=CascadeType.ALL
or
cascade={CascadeType.PERSIST, CascadeType.MERGE}
I know merge will persists newly created entities and should behave as persists, but these are my best hints.
What you wantto achieve you can achieve with this code.
#Entity
#Table(name = "PARENT")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long parentId;
#OneToMany(cascade = CascadeType.PERSIST)
private List<Child> children;
}
#Entity
#Table(name = "CHILD")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#ManyToOne
Parent parent;
}
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
#Entity
#Table(name = "parent")
public final class Parent extends Base {
#OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
private Person person;
and doing (amongst other things) this :
Parent parent = new Parent();
Person person = new Person();
parent.setPerson(person);
session.save(parent);
I get the mentioned exception ?
Do I manually need to call session.save(person) before ? do I have to add a cascade type annotation to the childs class definition(where it references the parent) ?
Or have I missed something else obvious ?
I don't want to use CascadeType.ALL as when a parent is deleted I want to keep the person(child).
Both entities/tables extend a common Base table :
#MappedSuperclass()
public abstract class Base {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
public Integer getId() {
return id;
}
Will this effect which cascade type is required ?
You haven't mentioned the Hibernate version, but this hasn't changed since I ever started using it.
As you can read in the Hibernate reference, to get the Java standard SAVE_UPDATE you need {CascadeType.PERSIST, CascadeType.MERGE} in Hibernate.
EDIT: Seeing the updated info, what you're doing now causes Hibernate to treat it as a bi-directional one-to-one mapping. This basically means that for each object in any of those two tables, there has got to be a counterpart in the other table with the same ID. Therefore, you cannot delete only one of them, you would lose FK integrity.
If you want it to be a unidirectional mapping, e.g., if you want to be able to delete the person but leave the parent -- you have to specify a FK, usually via #JoinColumn, like
#JoinColumn(name="PERSON_ID", unique=false, nullable=true, insertable=true, updatable=true)
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.