Why can orphan removal be not executed? - java

There is some code in a Spring Boot project (simplified for brevity) :
#Transactional
public void serviceMethod() {
var child = Child.builder().value(...).build();
var parent = parentEntityRepository.findById(5);
child.setParent(parent);
parent.getChildren().clear();
parent.getChildren().add(child);
}
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL,orphanRemoval = true)
#Fetch(FetchMode.SUBSELECT)
private List<Child> children = new ArrayList<>();
}
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
// this field has UNIQUE constaint in the project's Postgres DB
private String value;
}
and child_table has UNIQUE index on the one of its columns.
I can't understand, why it doesn't work, failing with "unique contstraint violation error".
As I read here, Hibernate flushing order starts with OrphanRemovalAction, so the code above seems to be correct, but while checking Hibernate logs with generated native SQL there were not any DELETE statements issued after transaction had been commited.

There is a known Hibernate bug describing this problem.
Currently, the cascade executes the INSERT of the new entity before the DELETE (ophan-removal).
To work around it, you may just flush the session once after clearing the list. This way the orphan-removal is done before also cascading a persist to a newly associated child (which triggers its insertion).

Related

JPA Parent Child works. Insert child alone

I am new to JPA. I have parent and several children. I am able to successfully save parent and associated child with Column Mapping using below code.
#Data
#Entity
#Table(name = "Parent")
public class Parent{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ParentKey", nullable = false, length = 20)
private Long parentKey;
#Column(name = "ParentID", nullable = true, length = 60)
private String parentID;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "ParentKey", nullable = false)
private Set<Child> childSet = new HashSet<>(0);
}
#Data
#Entity
#Table(name = "Child")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long childKey;
private String ChildID;
}
I have a scenario where I receive only child records with parent ID. I lookup parent and want to use same child object and repository for saving additional child records. Since there is no reference for parent key, how can I achieve without duplicating child object.
Child Object does not have ParentKey. I do not want to make any changes to Parent when I am loading child records only.
I am using mySQL.
Thanks
First, you need a way to query DB that will get you Parent entity by parentID. If you are using spring data jpa module, you can add the following method to a ParentRepository
Optional<Parent> findByParentID(String parentID);
Assuming your service method looks like this, you first retrieve Parent from DB using parentID and then you can add children to that parent.
public void saveChildren(String parentID, Set<Child> children) {
Parent parent = parentRepository.findByParentID(parentID)
.orElseThrow(() -> <Your excp that says parent not found by parentID>);
// assuming you have filtered out duplicates, now you can save remaining as
parent.getChildSet().addAll(children);
parentRepository.save(parent);
}

JPA OneToMany/ManyToOne relationship not working - What am I missing?

I know this has been asked a lot of times before, I know it because I've searched for every related question to my problem to try to find a solution, however, none of the proposed solutions are working for me and I'm pretty sure that I have to be missing something.
Person Class:
#Entity
#Table(name = "person", schema = "test")
public class PersonEntity {
#Id
#Column(name = "id")
private long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "age")
private int age;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "personid")
private List<ProjectEntity> projects;
}
Project Class:
#Entity
#Table(name = "project", schema = "test")
public class ProjectEntity {
#Id
#Column(name = "id")
private long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "budget")
private int budget;
#JoinColumn(name = "personid", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.ALL)
private PersonEntity personid;
}
I have a bidirectional OneToMany/ManyToOne relationship, I have tried changing
the cascade type to PERSIST, adding 'optional=false' and way more things but nothing seems to work.
I read that I have to 'join' manually the entities before the persist, and that's what I did:
em = JPAUtility.getEntityManager();
em.getTransaction().begin();
PersonEntity personTest = new PersonEntity();
personTest.setName("Test");
personTest.setAge(23);
ProjectEntity projectTest = new ProjectEntity();
projectTest.setName("hello");
projectTest.setBudget(232);
projectTest.setPersonid(personTest);
List<ProjectEntity> projects = new ArrayList<ProjectEntity>();
projects.add(projectTest);
personTest.setProjects(projects);
em.persist(personTest);
em.getTransaction().commit();
em.close();
return personTest;
But I still get this:
Caused by:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot add or update a child row: a foreign key constraint fails
(`test`.`project`, CONSTRAINT `FK_Personid` FOREIGN KEY (`personid`) REFERENCES
`person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)
I honestly don't know what I'm missing, if anyone has any suggestion I'll be more than happy to try it.
Thank you so much!
SOLUTION
I managed to solve the problem thanks to all the suggestions, basically, I was missing the #GeneratedValue(strategy=GenerationType.AUTO) annotation which I removed because I thought it didn't work but, it wasn't working because I was missing a property on the persistence.xml:
<property name="hibernate.id.new_generator_mappings" value="false" />
I found this info here
You also need a method to add the relationship in the objects:
public void addToProjects(ProjectEntity project){
project.setPersonid(this);
this.projects.add(project);
}
To make this work you need to initialize the List when you declare the variable:
private List<ProjectEntity> projects = new ArrayList<ProjectEntity>();
And that's it!
This is the final working code in case anyone can find it useful :):
Person Class:
#Entity
#Table(name = "person", schema = "test")
public class PersonEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "age")
private int age;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "personid")
private List<ProjectEntity> projects = new ArrayList<ProjectEntity>();
public void addToProjects(ProjectEntity project) {
project.setPersonid(this);
this.projects.add(project);
}
}
Project Class:
#Entity
#Table(name = "project", schema = "test")
public class ProjectEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "budget")
private int budget;
#JoinColumn(name = "personid", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.PERSIST)
private PersonEntity personid;
public void setPersonid(PersonEntity personid) {
this.personid = personid;
}
}
Make sure you add the Children to their Parent and vice-versa (addToProjects())
em = JPAUtility.getEntityManager();
em.getTransaction().begin();
PersonEntity personTest = new PersonEntity();
personTest.setName("Butters");
personTest.setAge(10);
ProjectEntity projectTest = new ProjectEntity();
projectTest.setName("Hanks");
projectTest.setBudget(10000);
ProjectEntity projectTest2 = new ProjectEntity();
projectTest2.setName("X");
projectTest2.setBudget(100);
personTest.addToProjects(projectTest);
personTest.addToProjects(projectTest2);
em.persist(personTest);
em.getTransaction().commit();
em.close();
Hope it helps! Thank you so much.
The main thing that you will want to watch out for is to define the owning side of the relation correctly. As far as I remember, my takeaway from the (sometimes difficult to understand) official documentation was that the owning side is pretty much the one that will by default trigger cascades and transparent deletions.
For example, in the above, you have defined the owning side as ProjectEntity, so the most important step for cascaded persistence to work is to add the project to PersonEntity.projects.
You will then want to call persist on the owning side of the relation, i.e.
em.persist(projectTest);
If this doesn't help, I would suggest that you enable SQL logging in your JPA provider to find out what statements it is trying to execute, and especially in what order these entities are being inserted.
Also try, as per existing comments, to persist person first.
If you do this, I believe the correct way is to add the persisted entity to the relationship, i.e:
PersonEntity persistedPerson = em.persist(personTest);
projectTest.setPersonId(persistedPerson);
em.persist(projectTest);
A couple of leads I can think of, because I crossed more than once this kind of problems:
Unless you want a cascade operation from Project to update your Person, you should remove
#ManyToOne(cascade = CascadeType.ALL)
from your personid attribute
Try updating your projects collection instead of creating a new one, because if it's already managed by Hibernate (which doesn't need to be , the persist/merge operation will be executed on the old one and the new one.
Person Class:
private List<ProjectEntity> projects = new ArrayList<>();
your code :
personTest.getProjects().addAll(projects);
I usually prefer merge instead of persist, because I find it more 'natural', and sometimes, the output is clearly not the same.
I had the same problem. A #ManyToOne that was not working for no reason and 2 classes. I added #GeneratedValue(strategy=GenerationType.AUTO), but it didn't fix my problem.
I also tried to rename the classes, clean, close project, restart, etc., but none worked. At the end, I deleted the files (made a copy before) and recreated them from new and that fixed my problem. I was on Eclipse 4.8, Spring 2.5, Groovy 2.5, Java 1.8
UPDATE:
Not really sure what was the problem, anyway (for groovy) check your save method: myUserRepo(new MyUser("username")), check as well your xxxxRepo< MyUser, Integer> and also check that your file is .groovy (last one shouldn't be a problem)
Other UPDATE:
If you're creating a relational between 2 tables and use the save result, be sure to use #Transactional on a Service and link the relation field, for example:
#Transactional
UserAccount save(UserAccount userAccount) {
User user = userRepo.save(new User(userAccount))
UserAccount.setUser(user)
userAccountRepo.save(userAccount)
}

Spring data JPA doesn't delete entity

I have a problem with deleting entity from database. Whatever I do anyway it doesn't delete.
Driver class
#Entity
#Table(name = "drivers")
public class Driver {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "driver", fetch = FetchType.EAGER)
#JsonSerialize(using = RatingsSerializer.class)
private List<Rating> ratings;
// other fields. Getters and Setters...
}
Rating class
#Entity
#Table(name = "ratings")
#JsonDeserialize(using = RatingDeserializer.class)
public class Rating {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "driver_id")
private Driver driver;
#ManyToOne
#JoinColumn(name = "client_id")
private Client client;
private int mark;
private Date createdAt;
//Getters and Setters ...
}
First one what I do is annotate ratings with #OneToMany(mappedBy = "driver", fetch = FetchType.EAGER, orphanRemoval = true, cascade = CascadeType.REMOVE) and when call driverRepository.delete(driver) it throws:
org.postgresql.util.PSQLException: ERROR: update or delete on table "drivers" violates foreign key constraint "fk3raf3d9ucm571r485t8e7ew83" on table "ratings"
Ok, choose another way. Try to delete each rating object using ratingRepository, but never happens, it just iterate thorough each rating item and throw again error
org.postgresql.util.PSQLException
Next step was to set for each rating item Client and Driver to null. Now driver entity is deleted from database but rating entity remain in database.
What happens?
Spring Data JPA version: 1.5.7
It looks that your Foreign Key error is related to Client table which is linked according to your code line:
#ManyToOne
#JoinColumn(name = "client_id")
private Client client;
So, if you add cascade = CascadeType.REMOVE within the annotation, it may works. But, that' up to you if you want to delete everything on cascade including Client row. If not, then update that column value first to null.

JPA: unidirectional many-to-one and cascading delete

Say I have a unidirectional #ManyToOne relationship like the following:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
}
#Entity
public class Child implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn
private Parent parent;
}
If I have a parent P and children C1...Cn referencing back to P, is there a clean and pretty way in JPA to automatically remove the children C1...Cn when P is removed (i.e. entityManager.remove(P))?
What I'm looking for is a functionality similar to ON DELETE CASCADE in SQL.
If you are using hibernate as your JPA provider you can use the annotation #OnDelete. This annotation will add to the relation the trigger ON DELETE CASCADE, which delegates the deletion of the children to the database.
Example:
public class Parent {
#Id
private long id;
}
public class Child {
#Id
private long id;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
With this solution a unidirectional relationship from the child to the parent is enough to automatically remove all children. This solution does not need any listeners etc. Also a JPQL query like DELETE FROM Parent WHERE id = 1 will remove the children.
Relationships in JPA are always unidirectional, unless you associate the parent with the child in both directions. Cascading REMOVE operations from the parent to the child will require a relation from the parent to the child (not just the opposite).
You'll therefore need to do this:
Either, change the unidirectional #ManyToOne relationship to a bi-directional #ManyToOne, or a unidirectional #OneToMany. You can then cascade REMOVE operations so that EntityManager.remove will remove the parent and the children. You can also specify orphanRemoval as true, to delete any orphaned children when the child entity in the parent collection is set to null, i.e. remove the child when it is not present in any parent's collection.
Or, specify the foreign key constraint in the child table as ON DELETE CASCADE. You'll need to invoke EntityManager.clear() after calling EntityManager.remove(parent) as the persistence context needs to be refreshed - the child entities are not supposed to exist in the persistence context after they've been deleted in the database.
Create a bi-directional relationship, like this:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private Set<Child> children;
}
I have seen in unidirectional #ManytoOne, delete don't work as expected.
When parent is deleted, ideally child should also be deleted, but only parent is deleted and child is NOT deleted and is left as orphan
Technology used are Spring Boot/Spring Data JPA/Hibernate
Sprint Boot : 2.1.2.RELEASE
Spring Data JPA/Hibernate is used to delete row .eg
parentRepository.delete(parent)
ParentRepository extends standard CRUD repository as shown below
ParentRepository extends CrudRepository<T, ID>
Following are my entity class
#Entity(name = “child”)
public class Child {
#Id
#GeneratedValue
private long id;
#ManyToOne( fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = “parent_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
#Entity(name = “parent”)
public class Parent {
#Id
#GeneratedValue
private long id;
#Column(nullable = false, length = 50)
private String firstName;
}
Use this way to delete only one side
#ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
// #JoinColumn(name = "qid")
#JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = #ForeignKey(name = "qid"), nullable = false)
// #JsonIgnore
#JsonBackReference
private QueueGroup queueGroup;
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
Given annotation worked for me. Can have a try
For Example :-
public class Parent{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="cct_id")
private Integer cct_id;
#OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private List<Child> childs;
}
public class Child{
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="cct_id")
private Parent parent;
}
You don't need to use bi-directional association instead of your code, you have just to add CascaType.Remove as a property to ManyToOne annotation, then use #OnDelete(action = OnDeleteAction.CASCADE), it's works fine for me.

Hibernate generating SQL queries when accessing associated entity's id

I have Hibernate Entities that look something like this (getters and setters left out):
#Entity
public class EntityA {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private EntityB parent;
}
#Entity
public class EntityB extends SuperEntity {
#OneToMany(mappedBy = "parent")
#Fetch(FetchMode.SUBSELECT)
#JoinColumn(name = "parent_id")
private Set<EntityA> children;
}
#MappedSuperclass
public class SuperEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long itemId;
}
When I query for EntityA it loads fine, with the parent association being replaced by a Hibernate proxy (as it is Lazy). If I want access to the parent's id I perform the following call:
EntityA entityA = queryForEntityA();
long parentId = entityA.getParent().getItemId();
As I understand that call should NOT make a roundtrip to the database, as the Id is stored in the EntityA table, and the proxy should only return that value. However, in my case this generates a SQL statement which fetches EntityB and only then returns the Id.
How can I investigate the problem? What are some likely causes of this incorrect behaviour?
As I understand that call should NOT make a roundtrip to the database, as the Id is stored in the EntityA table, and the proxy should only return that value.
Use property access type. The behavior you're experiencing is a "limitation" of field access type. Here is how Emmanuel Bernard explained it:
That is unfortunate but expected. That's one of the limitations of field level access.
Basically we have no way to know that getId() indeed only go and access the id field. So we need to load the entire object to be safe.
So change your code into:
#Entity
public class EntityA {
private EntityB parent;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
public EntityB getParent() {
return parent;
}
...
}
#MappedSuperclass
public class SuperEntity {
private long itemId;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
public long getItemId() {
return itemId;
}
...
}
Related question
Hibernate Annotations - Which is better, field or property access?
References
Proxy loaded on getId-call when using annotations on fields
proxy getId => why sql is generated !
HHH-3718 (if this issue can ever be solved)
What you say makes sense - that it would not make a DB hit since EntityA contains the parent ID. I am just not sure if the getParent() call actually loads the EntityB object regardless of whether all you're interested in is the ID. You might try marking the children collection (and any other fields) as Lazy if you want to save the DB hit.
#Entity
public class EntityB : SuperEntity {
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
#Fetch(FetchMode.SUBSELECT)
#JoinColumn(name = "parent_id")
private Set<EntityA> children;
}
As for Hibernate:
This behavior has been changed since Hibernate 5.2.12.

Categories