JPA: unidirectional many-to-one and cascading delete
Asked Answered
P

8

130

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.

Perishable answered 25/8, 2011 at 21:6 Comment(3)
Even if only 'Child' has a reference to 'Parent' (in that way the referencing is unidirectional) is it problematic for you to add the List of 'Child' with a '@OneToMany' mapping and 'Cascade=ALL' attribute to the 'Parent'? I assume that JPA should resolve that even tough only 1 side holds the reference.Scoggins
@kvDennis, there are cases where you don't want to tightly couple the many-side to the one side. E.g. in ACL-like setups where security permissions are transparent "add-on"Medford
Check this answer if you are on Spring Boot 3: https://mcmap.net/q/175361/-error-update-or-delete-on-table-quot-tablename-quot-violates-foreign-key-constraintEnergetic
C
86

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.
Clevis answered 26/8, 2011 at 5:6 Comment(3)
is there a way to do No2 with a JPA annotation?Trophic
How do I do No2 with Hibernate xml mappings?Ockham
Yes, there is. Add the following on private Parent parent; in Child: @JoinColumn(name = "parentId", foreignKey = @ForeignKey(value = CONSTRAINT, foreignKeyDefinition = "FOREIGN KEY (parentId) REFERENCES parent(id) ON DELETE CASCADE"))Cudbear
D
133

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.

Displant answered 21/7, 2016 at 4:59 Comment(8)
I can't make it work this way, is there any specific version of hibernate or other more detailed example like this?Orwin
It's hard to say why it is not working for you. To get this working you may need to regenerate the schema or you have to add the cascade delete manually. The @OnDelete annotation seems to be around for awhile as such I would not guess that the version is an issue.Displant
Thanks for the answer. Quick note: the database cascade trigger will only be created if you have enabled DDL generation via hibernate. Otherwise you'll have to add it another way(e.g. liquibase) to allow ad hoc queries run directly against the DB like 'DELETE FROM Parent WHERE id = 1' perform cascade removal.Clique
this is not working when the association is @OneToOne Any ideas how to solve it with @OneToOne?Carlsen
@ThomasHunziker this will not work for orphanRemoval right?Matronly
It does not work when you trigger the deletion through the database. However if you use hibernate to remove the entity it will work.Displant
For a database migration (i.e., adding ON DELETE CASCADE to an existing db constraint) this is pretty useful https://mcmap.net/q/175362/-how-to-change-the-foreign-key-referential-action-behaviorMyrmecology
It works with @OneToOne I have just tested it. Basically this annotation sets on delete cascade action on a foreign key costraintGaillardia
C
86

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.
Clevis answered 26/8, 2011 at 5:6 Comment(3)
is there a way to do No2 with a JPA annotation?Trophic
How do I do No2 with Hibernate xml mappings?Ockham
Yes, there is. Add the following on private Parent parent; in Child: @JoinColumn(name = "parentId", foreignKey = @ForeignKey(value = CONSTRAINT, foreignKeyDefinition = "FOREIGN KEY (parentId) REFERENCES parent(id) ON DELETE CASCADE"))Cudbear
P
16

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;
}
Presbyter answered 18/6, 2012 at 1:28 Comment(9)
bad answer, bidirectional relationships are terrible in JPA because operating on large children sets takes incredible amount of timePostpaid
Is there proof that bidirectional relationships are slow?Slipslop
@enerccio What if the bidirectional relationship is one-to-one? Also, please show an article that states bi-directional relationships are slow? slow in what? retrieving? deleting? updating?Hubbs
@Hubbs every operation (add, remove) will load all children, so that is huge data load that can be useless (like adding a value doesn't require loading all children from database which is exactly what this mapping does).Postpaid
@Postpaid I think everyone use lazy loading on joins. So how is it still a performance issue?Hubbs
@Hubbs lazy/eager only change the dynamics WHEN list is loaded, eager loads it right away when entity is loaded, lazy loads it on first access. Problem is that even if you are only adding new value to the lazy loaded list the list will be loaded, which means even simple add new child is N database operation, which is why I ditched OneToMany operations and instead use id raw mapping if I know sets will be large.Postpaid
Can confirm. a bidirectional, optional one to one relationship that i have in my code causes N select statements to run whenever i try to findAllSpirant
As Vlad says ... and hopefully I do not misquote him ... OneToMany should be called OneToFew ... bidirectional relationships are preferable however as long as we're not talking about a OneToMany where the Many is too large ... get his much better explanation here ... vladmihalcea.com/…Ashurbanipal
But talking about children...this should not exceed 10? Is that still a performance issue? Or is it worth the downside of not being able to navigate from Children to Parent via one simple join? (sidenote: this example is a bit off because a child would only be able to have exactly one parent)Sabrasabre
L
3

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;


}
Luwian answered 11/2, 2019 at 15:57 Comment(3)
I found the solution to why delete was not working. apparently hibernate was NOT using mysql Engine -INNODB , you need engine INNODB for mysql to generate foreign key constraint. Using the following properties in application.properties, makes spring boot/hibernate to use mysql engine INNODB. So foreign key constraint works and hence also delete cascadeLuwian
Missed properties use in earlier comment. following are spring properties used spring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect Luwian
FYI, you have wrong " in the code. See name= "parent"Alphabetic
S
1

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.

Skater answered 18/9, 2020 at 1:52 Comment(0)
C
0

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;
Chapa answered 9/9, 2019 at 13:47 Comment(0)
T
0

As others mentioned, in hibernate you can use @OnDelete with all limitations it have. Unfortunately there is no option to do so in JPA, workaround could be to define this rule directly in schema, on foreign key definition (for example in liquibase syntax):

<addForeignKeyConstraint onDelete="CASCADE" baseTableName="child" baseColumnNames="parent_id" constraintName="child_parent_id_fk" referencedTableName="parent" referencedColumnNames="id"/>
Terrell answered 29/1 at 9:57 Comment(0)
N
-1

@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;
    }
Nicollenicolson answered 25/2, 2016 at 9:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.