126

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.

asherbret
  • 5,439
  • 4
  • 38
  • 58
perp
  • 3,883
  • 4
  • 31
  • 29
  • 1
    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. – kvDennis Aug 25 '11 at 21:32
  • 1
    @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" – Bachi Nov 13 '13 at 19:47
  • Check this answer if you are on Spring Boot 3: https://stackoverflow.com/a/70427325/4428219 – pyfyc May 11 '23 at 05:34

7 Answers7

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.

Jens Piegsa
  • 7,399
  • 5
  • 58
  • 106
Thomas Hunziker
  • 1,928
  • 1
  • 16
  • 13
  • 6
    I can't make it work this way, is there any specific version of hibernate or other more detailed example like this? – Mardari Jan 18 '17 at 15:42
  • 4
    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. – Thomas Hunziker Jan 18 '17 at 19:28
  • 13
    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. – mjj1409 Mar 18 '17 at 11:38
  • 1
    this is not working when the association is `@OneToOne` Any ideas how to solve it with `@OneToOne`? – stakowerflol Dec 21 '18 at 14:24
  • 1
    @ThomasHunziker this will not work for orphanRemoval right? – oxyt Feb 05 '19 at 15:20
  • It does not work when you trigger the deletion through the database. However if you use hibernate to remove the entity it will work. – Thomas Hunziker Feb 08 '19 at 13:40
  • For a database migration (i.e., adding ON DELETE CASCADE to an existing db constraint) this is pretty useful https://stackoverflow.com/a/14381227/2377044 – cnmuc Jul 21 '22 at 19:48
  • It works with @OneToOne I have just tested it. Basically this annotation sets on delete cascade action on a foreign key costraint – Alex Shavlovsky Apr 03 '23 at 17:57
84

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.
Vlasec
  • 5,500
  • 3
  • 27
  • 30
Vineet Reynolds
  • 76,006
  • 17
  • 150
  • 174
  • 10
    is there a way to do No2 with a JPA annotation? – user2573153 Feb 13 '14 at 23:26
  • 4
    How do I do No2 with Hibernate xml mappings? – arg20 Mar 15 '14 at 05:01
  • 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"))` – Bram Janssens Jun 20 '23 at 08:32
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;
}
tekumara
  • 8,357
  • 10
  • 57
  • 69
  • 12
    bad answer, bidirectional relationships are terrible in JPA because operating on large children sets takes incredible amount of time – Enerccio Dec 26 '17 at 07:15
  • 2
    Is there proof that bidirectional relationships are slow? – shalama Nov 01 '19 at 09:12
  • 1
    @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? – saran3h May 08 '20 at 05:44
  • @saran3h 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). – Enerccio May 08 '20 at 14:57
  • 2
    @Enerccio I think everyone use lazy loading on joins. So how is it still a performance issue? – saran3h May 08 '20 at 16:11
  • @saran3h 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. – Enerccio May 08 '20 at 21:48
  • 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 findAll – Alan May 09 '22 at 14:48
  • 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 ... https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/ – James Gawron Jun 02 '22 at 23:17
  • 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) – ch1ll Apr 07 '23 at 06:22
2

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;


}
ranjesh
  • 61
  • 1
  • 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 cascade – ranjesh Feb 11 '19 at 22:13
  • 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 ``` – ranjesh Feb 11 '19 at 22:21
  • FYI, you have wrong `"` in the code. See `name= "parent"` – alexander Mar 27 '20 at 16:00
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.

Chedy
  • 57
  • 1
  • 4
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;
Shubham
  • 707
  • 9
  • 7
-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;
    }
Swarit Agarwal
  • 2,520
  • 1
  • 26
  • 33