Orphan removal with #Where clause - java

I have a hierarchical structure. Some children are hidden so I have #Where clause:
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true)
#org.hibernate.annotations.Where(clause = "hidden=false")
private List<Element> children;
But when I want to remove the whole sub-structure (including hidden children) by deleting parent node, hibernate seems to skip hidden children and i get db exception:
org.postgresql.util.PSQLException: ERROR: update or delete on table "xxx" violates foreign key constraint "yyy_yyy_id_fkey" on table "zzz"
Does hibernate have some method of dealing with it?

Related

Hibernate Cascade DELETE OneToMany does not work. Referential integrity constraint violation

I have a class Webtoon that contains a list of Episode. It is a one direction relation.
#OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#CollectionTable(name= "list_of_episodes")
List<Episode> listOfEpisodes = new ArrayList<>();
In my Unit test, I created a Webtoon object, then added an episode in the list listOfEpisodes.
When I try to delete the Episode using the Episode repository
this.episodeRepo.delete(episode);
I got the error :
violation de contrainte: "FK50GHKTDAXMN68TBU6KAYVUX9S:
PUBLIC.LIST_OF_EPISODES FOREIGN KEY(LIST_OF_EPISODES_ID) REFERENCES
PUBLIC.EPISODE(ID) (3)"
Referential integrity constraint violation: "FK50GHKTDAXMN68TBU6KAYVUX9S: PUBLIC.LIST_OF_EPISODES FOREIGN
KEY(LIST_OF_EPISODES_ID) REFERENCES PUBLIC.EPISODE(ID) (3)"; SQL
statement:
delete from episode where id=? [23503-200]
Why hibernate can't remove this object and update the list in Webtoon class ?
Try to change FetchType from EAGER to LAZY
Referential integrity is a property of data stating that all its references are valid. In the context of relational databases, it requires that if a value of one attribute (column) of a relation (table) references a value of another attribute (either in the same or a different relation), then the referenced value must exist.
From the error PUBLIC.LIST_OF_EPISODES FOREIGN KEY(LIST_OF_EPISODES_ID) REFERENCES PUBLIC.EPISODE(ID) you can clearly see that PUBLIC.EPISODE(ID) is referenced as a foreign key in PUBLIC.LIST_OF_EPISODES table so you cannot delete a parent unless you delete a child element.
Try using #JoinColumn instead of using #CollectionTable(name= "list_of_episodes")
To correct this issue, I first changed the uni-directional relation to bi-directional.
In Webtoon I have :
#OneToMany(cascade = CascadeType.ALL, mappedBy="webtoon", orphanRemoval = true)
#CollectionTable(name= "list_of_episodes")
List<Episode> listOfEpisodes = new ArrayList<>();
In Episode, I added a webtoon attribute
#ManyToOne(fetch= FetchType.LAZY)
#JoinColumn(name="webtoon_id")
Webtoon webtoon;
Because, it is lazy...I could not get the list as if I'm using eager, so I added a join select in my repository. And also when I delete, I have the cascade delete.

ManyToOne relation, parent can update/remove/add childs, but parent list is also updated when child is saved/removed, without cascading

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?

How to force Hibernate to remove orphans before update

Let's say I have following model structure:
#Entity
#Table(....)
public class AnnotationGroup{
...
private List<AnnotationOption> options;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "annotation_group_id", nullable = false)
public List<AnnotationOption> getOptions() {
return options;
}
}
#Entity
#Table(...)
public class AnnotationOption {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Override
public Long getId() {
return id;
}
}
At the moment I have group1 with AnnotationOptions opt1 opt2 and opt3
Then I want to replace all option with only one option opt1
Additionally I have constraint in database:
CONSTRAINT "UQ_ANNOTATION_OPTION_name_annotation_group_id" UNIQUE (annotation_option_name, annotation_group_id)
And this one fires:
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "UQ_ANNOTATION_OPTION_name_annotation_group_id"
Detail: Key (name, annotation_group_id)=(opt1, 3) already exists.
Actually isuue that hibernate removes orphans after update.
Can you suggest something t resolve issue?
There are so many things that are wrong in this example:
EAGER fetching on the #OneToManycollection is almost always a bad idea.
Unidirectional collections are also bad, use the bidirectional one.
If you get this exception, most likely you cleared all the elements and re-added back the ones that you want to be retained.
The best way to fix it is to explicitly merge the existing set of children with the incoming ones so that:
New child entities are being added to the collection.
The child entities that are no longer needed are removed.
The child entities matching the business key (annotation_group_name, study_id) are updated with the incoming data.
According to Hibernate documentation hibernate perform in the following order to preserve foreign-key constraint:
Inserts, in the order they were performed
Updates
Deletion of collection elements
Insertion of collection elements
Deletes, in the order they were performed
For your special need you should manually flush the transaction to force the deletion in database before.

JPA Merge on OneToMany and Cascade.ALL making unwanted select on child tables

I am using Hibernate 3.5 JPA and have doubt about specific behavior by this implementation.
We have Parent which has two child with below property
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", fetch = FetchType.LAZY)
private List<ChildOne> childOneList;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", fetch = FetchType.LAZY)
private List<ChildTwo> childTwoList;
And ChildOne with below property for referencing Parent (Assume even ChildTwo with same code as below)
#JoinColumn(name = "PUID", referencedColumnName = "PUID")
#ManyToOne
private Parent parent;
I am persisting parent object using JPA EntityManager.merge(PARENT), at this time I don't have child of this specific parent, however this child table has many records which are NOT related to this parent record. (Linked child record will be created after some time)
parent.setChildOneList(null);
parent.setChildTwoList(null);
em.merge(parent);
What happens is, on EntityManager.merge, it first fire select query with "left outer join" on childs (which fetches all unrelated records) and then apply insert/update on parent record. SQL queries in log like.
Hibernate: select parent0_.PUID as PUID96_1_,childoneli1_.PUID as PUID96_3_,childoneli1_.CHILD_UID as CHILD1_3_,childoneli1_.CHILD_UID as CHILD1_97_0_,childoneli1_.PUID as PUID97_0_ from PARENTT parent0_ left outer join CHILDONE childoneli1_ on parent0_.PUID=childoneli1_.PUID where parent0_.PUID=?|#]
Hibernate: insert into PARENTT (PUID) values (?)|#]
This is temporarily solved by removing the Cascade.ALL. Now, I may need this in future so, wanted to know any pointers on this behavior by Hibernate JPA implementation. (Annotation Import has all javax.... no Hibernate specific).
If this is what expected from Hibernate JPA then and please point me on how to correct it with Cascade.ALL and without unnecessary "left outer join" select on merge call.

JPA: implicit cascades for relationships mapped as #ManyToMany #JoinTable?

I have the following mapping:
#Entity
#Table(name = "Prequalifications")
public class Prequalification implements Serializable
{
...
#ManyToMany
#JoinTable(name = "Partnerships", joinColumns = #JoinColumn(name = "prequalification_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "company_id", referencedColumnName = "id"))
private Set<Company> companies;
...
}
In a #ManyToMany + #JoinTable mapped relationship, isn't it kind of implicit that the association (link) entities (here Partnerships) are automatically persisted, removed, etc. even though
by default, relationships have an empty cascade set
? The above quote was taken from "Pro JPA 2, by Mike Keith".
Executing
em.merge(prequalification);
on the above entity does persist the associated partnerships without any cascade types specified.
Am I correct that this implicit cascade has to be performed? This isn't mentioned anywhere I looked...
The rows in the join table will be inserted/deleted as part of the owning Entity (if bi-directional the side without the mappedBy). So if you persist or remove or update the Prequalification the join table rows will also be inserted or deleted.
The target Company objects will not be cascaded to. So on remove() they will not be deleted, if the list is updated they will not be deleted unless orphanRemovla is set. Persist should also not be cascaded, but what happens when you have references to "detached" objects is somewhat of a grey area. Technically an error should be thrown, because the object is new and the relationship was not cascade persist. It may also try to insert and get a constraint error. It should not cascade the persist, although your object model is technically in an invalid state, so what occurs may depend on the provider.
Wanted to add a comment, but don't have enough rep for it.
I had the same question as #D-Dá´™um: "Where in the docs can we find a reference to this behaviour?"
I found it in the Hibernate docs (many-to-many).
If you scroll just a bit just below the code example there, you will find:
When an entity is removed from the #ManyToMany collection, Hibernate simply deletes the joining record in the link table. Unfortunately, this operation requires removing all entries associated with a given parent and recreating the ones that are listed in the current running persistent context.
Where the "link table" refers to the "join table".
Hope this helps.

Categories

Resources