Ok I have a 1-to-many mapping between a parent and child class. I can save the parent and it will automatically save the children objects, but problem is when doing a SELECT on the parent class. It seems that I'm getting a Parent object for every Child object in database table.
So if I save 1 parent object with 2 child objects, when I use Hibernate select Criteria I get 2 Parent objects!!!
All I want is for Hibernate to return 1 parent object with its 2 child objects inside the Set child field.
My mappings must be wrong I guess. Can someone please help with this?
class Parent{
Long parentId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "parent_table_id", nullable = false)
Set childs;
....
}
class Child{
Long childId;
}
Note: the "parent_table_id" references the Parent primary key. Also this value is not mapped into any Parent or Child object. I manually insert this value and only use it in the #JoinColumn annotation.
Ok I'm new to this JPA stuff but it seems that Hibernate is automatically inserting the values for field "parent_table_id" in the Child table when I save a Parent with Child objects.
Could this be causing the problem?
Your mapping looks fine, it sounds like what you actually need is:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
as you're performing an inner join that results in more than one result row being returned in your criteria query.
Related
I have an owning entity class with some associated entities, e.g.
#Entity
public class Parent {
#Id
#GeneratedValue
private UUID id;
#OneToMany(mappedBy = "parentId", cascade = CascadeType.ALL)
List<Child> children;
...
}
#Entity
public class Child {
#Id
#GeneratedValue
private UUID id;
private UUID parentId;
...
}
I am using Spring Boot Starter Data JPA. I traced the code to the SimpleJpaRepository class, I noticed that on calling save(parent), it checks if isNew() returns true, Spring will call persist(); otherwise it calls merge(). This makes total sense, as persist() will generate only an INSERT, while merge() will generate a SELECT (if it hasn't done before) and then followed by an INSERT if the SELECT returns nothing; otherwise an UPDATE.
The above works well when saving a new Parent with new Child, only INSERTs are generated without any SELECT.
However, my problem is, when creating some new Child and adding them to an existing Parent, then on saving the parent, somehow I noticed Spring JPA is still generating an extra SELECT for each of these new Child entities before the INSERTs, which I found unnecessary.
Is there a way to avoid these SELECT queries?
Further investigation I found that if I leave the id of the Child null (i.e. let it auto generates a new id), then only INSERT is generated. However, if I manually assign an id to a Child, then a SELECT will be generated before an INSERT. Is there a way I can assign id to Child while avoiding the extra SELECT?
On the basis of your code i think because of OnetoMany mapping and you did't provide cascade type then it not happen
select query running two times
provide more information
full code of Entity classes
properties file
I have 2 classes (ONE to MANY mapping).
Parent class:
#Entity
#Table(name = "parent")
public class Parent{
#Id
private int ParentId;
#OneToMany(cascade=CascadeType.ALL, mappedBy = "parent")
private List<Child> childEntities;
Child class:
#Entity
#Table(name = "child")
public class Child{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int childId;
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REFRESH, CascadeType.MERGE})
#JoinColumn(name="parent_id")
private Parent parent;
When I want to save parent object do database with parentRepository.save(parent) everything is right. Parent and Child are saved into database. The problem is, when I want to perform parentRepository.save(parent) on parent that is already in the database (JPA will do update - it updates parent table, but in child table it inserts new rows). The parent table is right, but in child table I have duplicate values. Primary Key in child is SERIAL (I use Postgres db).
Lets say I have empty database and I have these two lines of code:
parentRepository.save(parent);
parentRepository.save(parent);
After these two lines I will have single row in table parent in database, but every of his children is in table child twice.
Given that you are using #GeneratedValue, the child entity id will be generatet when you call sava for the first time.
If you check the save method documentation, you will see that it does return a new instance of the object you saved.
This new instance contains the parent entity with the child entities with the ids generated by spring on those child entities.
Another important change you need to do is to use wrapper types instead of primitives in the attributes annotated with #Id. Wrappers can contain a null value, this is important for spring to know if the entity is a new entity or is an entity to be updated. Check spring-data-jpa docs for more information.
The problem with your code is first that you are using primitive type attributes annotated with #Id (change it to wrapper types) and the second is that you are supposed to use the returned object from the save method for further operations.
Imagine an scenario like:
CREATE TABLE PARENT (id BIGINT, name VARCHAR);
and
CREATE TABLE CHILD (id BIGINT, name VARCHAR, parent_id BIGINT FOREIGN KEY REFERENCES PARENT(id));
This, translated in JPA entities, would be something like:
Parent.java:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
private List<Child> child;
Child.java:
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST}) //Child should not modify parent (except for its List<> of childs)
#JoinColumn(name = "parentId", updatable = false, insertable = false)
private Parent parent;
I don't know how to achieve the following setting:
When parentRepository.saveOrMerge(parent) is executed, both parent and its child on the set should be persisted. If parent's child Set has been updated from db state, changes should applied to child table as well. (This is the orphanRemoval flag, I guess). If the state of a particular child has changed, this should be persisted as well (this is the CascadeType.ALL on the parent-side, I guess).
When parentRepository.remove(parent) is executed, both parent and its child should be removed. Like an ON DELETE CASCADE, anything that references parent should be deleted even if the managed entity had a Child Set cleared. I dont know if this is possible through JPA.
When childRepository.saveOrMerge(child) is executed, child should be persisted, and if the parent object is not persisted, it should be persisted as well. In case the parent is in db and the child has a modified instance, changes should be discarded. (I guess this is the updatable=false and insertable=true) flags
When childRepository.remove(child) is executed, child should be removed from db, AND from any parent's set in memory. If this was the only child the parent had, parent should not be deleted (I guess this is the omission of CascadeType.REMOVE on the child-side relation)
Currently with the previous code, bullet point #4 is not achievable. Is there anyway I can indicate parent's child set should be automatically updated if I childRepository.remove(child) only by means of JPA annotations in the entity ? I would like to avoid having to use parentRepository when I want to delete a child. I find unnatural the only way to achieve all of this is by removing my child from the parent set and saving parent. What's the point of a ChildRepository then, if updating the Set in the parent and saving the parent is enough for everything?
I have two entity like this:
#Entity
public class Parent {
#Id
#GeneratedValue
private Long id;
#LazyCollection(LazyCollectionOption.EXTRA)
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childs = new ArrayList<>();
...Getter & Setter
}
#Entity
public class Child {
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = false)
private Parent parent;
...Getter & Setter
}
When remove child from parent, hibernate will query all children of this parent:
parent.getChilds().remove(child);
I have 100000+ child link to the parent. query all children is very slow and unacceptable.
How to fix this question or use other way?
Hibernate has to fetch all the children in order to prepare proper SQL - needs to know IDs of children elements to delete.
So there is no way to do it using ORM in way you would like to use it, but if you ask yourself a question, how would you do this in plain SQL obvious solution would be
DELETE c FROM child c WHERE c.parent_id=parentId
Hibernate is able to generate such query, but it is done via HPQL. So if you have delcared bidirectional relationship between parent and child and you have child has parent property, then you could do the following using HPQL:
Query q=session.createQuery(`DELETE c FROM chilc WHERE c.parent=:parent`);
q.setObject("parent",parent);
q.execute(); // q.executeUpdate?
This way you wil delete all the children of parent without fetching them.
DISCLAIMER:
Given query may not be 100% accurate because i dont remember Hibernate's Session API anymore, but the overall glance on the solution is given and 100% valid. Just reffer to Hiberrnate's docs for details.
Use the sessions's delete method without using the Parent and cascading the update:
sesionFactory.getCurrentSession().delete(child);
Update
It is a good practice to perform merge before a delete like this to make sure its managed:
sesionFactory.getCurrentSession().merge(child);
Say there are two entities: Parent and Child, with #OneToMany mapping from Parent to Child.
class Parent {
#Column(name="id")
private Long id;
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private List<Child> children;
}
class Child {
#Column(name="id")
private Long id;
#Column(name="parent_id")
private Long parentId;
}
As you can see, in my case, the Child table stores a foreign key to the Parent's primary key. But I don't want that as bi-directional mapping in my Child entity.
The issue arises now is, I'm unable to set the parent_id in Child instances.
I've created instances like this:
Parent parent = new Parent();
parent.setChildren(Lists.newArrayList(new Child(), new Child()));
parentDomainService.save(parent);
Assuming that there is cascading on Parent end. This approach saves the Parent first, then saves the Child instances. And then it runs the update query on child instances to update the parent_id, as I see from the Hibernate show_sql logs. But surprisingly, after update query, I see for some of the child, the parent_id is null. That was surprising to me.
So, I went to handle that thing manually, and removed cascading. Then I saved the entities like this:
Parent parent = new Parent();
parent.setChildren(Lists.newArrayList(new Child(), new Child()));
parent = parentDomainService.save(parent);
for (Child child: parent.getChildren()) {
child.setParentId(parent.getId());
}
childDomainService.save(parent.getChildren());
This one bounced back on me with following exception:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.somepkg.Child
I've seen many questions on SO with that exception, and I know there are many out there, but almost all of them are dealing with bi-directional mapping, or uni-directional mapping with JoinTable. Neither of them suits my situation.
Any lights on this? I'm out of options.
P.S.: The actual scenario I'm dealing with requires saving huge amount of data. E.g.: 50000 parent records, and 250000 Child records. That is why I don't want bi-directional mapping. Because saving Child will do create a query with join table in the back-end.
I'm mostly interested in solution, wherein I don't have to fire query twice on Child table. As that is happening in my current application, and that is hampering the performance.
When you remove cascading the parent does not persist the referenced child elements and at
parent = parentDomainService.save(parent);
the parent references the "unsaved transient" child instances and therefore throws the exception. If you first save the parent and then add the children:
Parent parent = new Parent();
parent = parentDomainService.save(parent);
parent.setChildren(Lists.newArrayList(new Child(), new Child()));
for (Child child: parent.getChildren()) {
child.setParentId(parent.getId());
}
childDomainService.save(parent.getChildren());
then the exception will not be thrown.