I have inherited a code base on which nearly all relations have the following annotations:
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, mappedBy = "someThing")
#OnDelete(action = OnDeleteAction.CASCADE)
Now I'm having trouble understanding what #OnDelete does in the first place. Hibernate: OnDelete vs cascade=CascadeType.REMOVE is interesting, but unfortunately doesn't have any answers and the JavaDoc for #OnDelete is particularly worthless.
From the other questions it looks like the OnDelete annotation somehow lets the DB do the cascading, while the cascading directive on #OneToMany let's the ORM do it, but what would ever be the purpose of using them together?
And does #OneToMany's cascade directive really doesn't allow the ORM implementation to generate a DB based cascade anyway?
Let's say you have a one-to-one directional relationship
class House {
#OneToOne
Object door;
}
If you use CascadeType.REMOVE then deleting the house will also delete the door.
#OneToOne(cascade=CascadeType.REMOVE)
Object door;
If you use #OnDelete then deleting the door will also delete the house.
#OneToOne
#OnDelete(action = OnDeleteAction.CASCADE)
Object door;
Read more here: https://rogerkeays.com/jpa-cascadetype-remove-vs-hibernate-ondelete
Related
I am using Hibernate as an ORM framework.
I have a bidirectional relationship that is implemented in Java as:
#Entity
#Table(name = "Parent")
class Parent {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy="parent", orphanRemoval=true)
private List<Child> child;
}
#Entity
#Table(name = "Child")
class Child {
...
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parent_id")
private Parent parent;
}
Further the relational database table "Child" does have the following foreign key specification
fk_child_parent FOREIGN_KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE
Question 1: Is it bad practice that the child class's foreign key is annotated with cascade = CascadeType.All? Based on my current understanding, I would assume that whenever I delete a child, I cascade the transaction and delete the parent as well.
Since the parent might have more than one child, this will leave some orphan childs which will be removed due to the orphanRemoval=true option. Is this correct?
Question 2: In Java I already specify the cascade operation from the parent down to the children with #OneToMany(cascade = CascadeType.ALL, ...). Is the SQL line, or at least the ON DELETE CASCADE part of it, superfluous?
Vice versa, if I have the SQL line ... ON DELETE CASCADE that specifies the foreign key, can I skip the #JoinColumn or #ManytoOne annotations?
Please excuse if this is trivial question. I am new to databases / ORM, trying to wrap my head around the concepts.
Using CascadeType.ALL is like using all of the CascadeType values i.e. CascadeType.PERSIST, CascadeType.MERGE etc.
Every CascadeType refers to an operation on the EntityManager. If you do entityManager.persist(parent) then Hibernate will automatically do entityManager.persist(child) for every element in the list if you CascadeType.PERSIST is enabled. Same goes for other cascade types.
How do i delete all the entries using hibernate deleteAll() ?
I have a class with multiple #oneToMany relationships (having like +5000 child entities) and when i try to do deleteAll i get the title error
oracle.jdbc.OracleDatabaseException: ORA-02292: integrity constraint (xxx) violated - child record found
I've tried adding
cascade = {CascadeType.ALL}
and
orphanRemoval=true
to #OneToMany relationship class, but no help.
It's a bidirectional relationship with following classes
#OneToMany(targetEntity = XXX.class, fetch = FetchType.LAZY, cascade = {CascadeType.ALL}, orphanRemoval=true, mappedBy = "zzz")
#Fetch(FetchMode.SELECT)
#JsonManagedReference
private List<XXX> xxx;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(targetEntity = YYY.class, fetch = FetchType.LAZY, orphanRemoval=true, cascade = {CascadeType.ALL}, mappedBy = "zzz")
#Fetch(FetchMode.SELECT)
#JsonManagedReference
private List<YYY> yyy;
with child elements like
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
#JoinColumn(name = "XXX", nullable=false)
#JsonBackReference
private XXX zzz;
i also tried HQL DELETE query but that dosent get me anywhere either.
How on earth do i delete all these entities consistently?
So far i've manually droped the tables since this problem started (all entities were deleted fine just few days ago) but thats starting to really annoy me, but i cant figure how to do this.
Thanks!
You have set CascadeType.ALL on your parent and the best way to delete should be call one single delete on parent entity
If you try to delete a child, it can be hibernate will propagate delete on a parent that has still children not still deleted.
Last resort with this king of problem is:
Enable logs on Spring Boot Application
Run sql query generated in SQL server
Find where the error happens evaluating the current database condition
Change JPA if necessary
I have problem with annotation #OnDelete with #OneToMany relation.
public class Patent {
#OneToMany
#JoinCollumn(name = "parent_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Child> children;
}
public class Child {
}
When I run it I get this error: "only inverse one-to-many associations may use on-delete="cascade"". How I need to change code to get it functional, without bidirectional relation? I know that, it can be solved with adding #ManyToOne relation, with appropriate annotations, to Child class, but I do not want to use this solution.
Edit: Purpose for this is that i need to generate "on delete cascade" to foreign key constraint in exported ddl schema.
All you need is to use orphanRemoval parameter for your OneToMany relation. See https://docs.oracle.com/cd/E19798-01/821-1841/giqxy/ for reference.
Example:
#OneToMany(mappedBy="customer", orphanRemoval="true")
public List<Order> getOrders() { ... }
However I think that your mapping is quite wrong, for such relation you should add Patent field to your Child class, mark relation as ManyToOne, then use JoinCollumn and set the reference as parent_id. With mappedBy and orphanRemoval options inside Patent - usability will be the same as you want.
I've got a classic case of Category <-> Category_Product <-> Product relation.
CategoryDTO class has Set<ProductDTO> member that defined as:
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "Category_Product",
joinColumns = #JoinColumn(name = "CAT_ID"),
inverseJoinColumns = #JoinColumn(name = "PROD_ID")
)
public Set<Product> getProducts() {
return products;
}
The problem is that I want to have Category->Product relation if and only if the Category_Product.ENABLED is '1'.
I tried to use #FilterJoinTable annotation but, as I understand, it works on entities only (not on linking table), so it doesn't help here.
I hope that there is an elegant solution that uses Hibernate built-in features.
Hibernate filters documentation is clear regarding #FilterJoinTable:
When the collection use an association table as a relational
representation, you might want to apply the filter condition to the
association table itself or to the target entity table. To apply the
constraint on the target entity, use the regular #Filter annotation.
However, if you want to target the association table, use the
#FilterJoinTable annotation.
So, it should work.
I'm currently moving a (working) app from using EclipseLink to Hibernate JPA, mostly it's gone quite smoothly, but I'm finding one thing that I can't explain, and also can't think of any good search terms!
Basically, I have four entities, with one-to-many relationships forming a chain:
EntityA has a list of EntityB's, each of which has a list of EntityC's, each of which have a list of EntityD's
each of those then has a many-to-one relationship going the other way, so:
EntityD has an EntityC, which has an EntityB, which has an EntityA.
That is (heavily reduced for clarity):
#Entity
public class EntityA {
#OneToMany (cascade = CascadeType.All, mappedBy = "entityA")
private List<EntityB> entityBList;
...
}
#Entity
public class EntityB {
#OneToMany (cascade = CascadeType.All, mappedBy = "entityB")
private List<EntityC> entityCList;
#JoinColumn (name = "ENTITY_A", referencedColumnName = "ENTITY_A_ID")
#ManyToOne (cascade = CascadeType.PERSIST, optional = false)
private EntityA entityA;
}
#Entity
public class EntityC {
#OneToMany (cascade = CascadeType.ALL, mappedBy = "entityC")
private List<EntityD> entityDList;
#JoinColumn (name = "ENTITY_B", referencedColumnName = "ENTITY_B_ID")
#ManyToOne (cascade = CascadeType.PERSIST, optional = false)
private EntityB entityB;
}
#Entity
public class EntityD {
#JoinColumn (name = "ENTITY_C", referencedColumnName = "ENTITY_C_ID")
#ManyToOne (cascade = CascadeType.PERSIST, optional = false)
private EntityC entityC;
}
I get an EntityA from the database (looking it up by its primary key), and thus get a nicely populated EntityA instance, with a PersistentBag for my List<EntityB>. I see a lazy load happening when I dereference that List<EntityB>, and the same repeated for getting EntityCs from EntityB.
At this point, everything is as I expect, I have an EntityA, B and C all fully populated with the values from the database, but then I try to get my EntityD, from EntityC, and find that it's null.
My entity manager is still open and active at this point, and even if I look at it in the debugger immediately after getting the EntityA, I can walk through the relationships, as far as EntityC, and again see the 'entityDList' as null.
The only solution I've found so far is to use:
EntityManager.refresh(entityC);
which populates all its elements including a lazily-loaded PersistentBag for the entityDList.
So, my guess is that Hibernate is only populating the references 2 levels deep (or 3, depending on how you count), and giving up after that, although I don't really understand why that would be. Does that make sense to anyone?
Is there any solution other than the .refresh? Some kind of config or annotation value that will make Hibernate populate the references all the way down?
Thanks to the suggestions from people here, which are probably relevant, but didn't help my specific case.
If you're reading this experiencing the same problem, it's probably worth trying the max_fetch_depth suggestion, but for some reason it didn't work for me (I'd love suggestions as to why?).
Likewise, if your #OneToManys are Sets, rather than Lists, doing an eager fetch or a left join, as suggested by Albert might work, but apparently Hibernate only lets you have a maximum of 1 List that is eagerly fetched, if you need more than that, your collections should be Sets. I didn't try it, but I suspect that it might have solved the problem.
Unless anyone has a better suggestion, I'll stick with calling refresh, which actually probably makes more sense for my application anyway.
This is funny indeed. One way to work around it would be to query object A left join-fetching to ->B->C->D which is also faster if your going to traverse down to object D anyway.
It would be something like this.
"from A left join fetch B left join fetch C left join fetch D"
Have you also tried making the relationship from C->D eager? Curious what will happen then...
The hibernate docs say that you can set it with the hibernate.max_fetch_depth property. The default is 3. You can find it in the "Hibernate Reference Documentation" on page 47.