JPA: односпрямоване багаторазове та каскадне видалення


95

Скажіть, у мене є односпрямовані @ManyToOne відносини, такі:

@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;  
}

Якщо у мене є батько P і діти C 1 ... C n, що посилаються назад на P, чи є чистий і гарний спосіб у JPA автоматично видалити дітей C 1 ... C n, коли P видалено (тобто entityManager.remove(P))?

Я шукаю - це функціонал, подібний до ON DELETE CASCADESQL.


1
Навіть якщо лише "Child" має посилання на "Parent" (таким чином посилання є однонаправленим), для вас проблематично додати список "Child" з відображенням "@OneToMany" і атрибутом "Cascade = ALL" 'батьків'? Я припускаю, що Спільна програма СР повинна вирішити, що навіть жорстка лише одна сторона має відношення.
kvDennis

1
@kvDennis, бувають випадки, коли ви не хочете щільно з'єднувати багатосторонні сторони в одну сторону. Наприклад, у налаштуваннях, схожих на ACL, де дозволи безпеки прозорі "надбудови"
Bachi

Відповіді:


73

Відносини в JPA завжди однонаправлені, якщо ви не пов'язуєте батька з дитиною в обох напрямках. Каскадні операції ВИДАЛЕННЯ від батька до дитини вимагатимуть відношення від батька до дитини (а не навпаки).

Тому вам потрібно буде зробити це:

  • Або змінити однонаправлене @ManyToOneвідношення на двонаправлене @ManyToOne, або однонаправлене @OneToMany. Потім ви можете каскадувати ВИДАЛЕННЯ операцій, щоб EntityManager.removeвилучити батьків та дітей. Ви також можете вказати orphanRemovalяк істинне, щоб видалити дітей-сиріт, коли дочірнє сутність у батьківській колекції встановлено на нуль, тобто видаліть дитину, коли її немає в колекції жодного з батьків.
  • Або вкажіть обмеження зовнішнього ключа в дочірній таблиці як ON DELETE CASCADE. Вам потрібно буде викликати EntityManager.clear()після виклику, EntityManager.remove(parent)оскільки контекст стійкості потрібно оновити - дочірні сутності не повинні існувати в контексті збереження після їх видалення в базі даних.

7
чи є спосіб зробити No2 з анотацією JPA?
користувач2573153

3
Як мені зробити No2 із зімованими xml відображеннями?
arg20

92

Якщо ви використовуєте сплячий режим як постачальник послуг JPA, ви можете використовувати анотацію @OnDelete. Це анотація додасть до відношення тригер ON DELETE CASCADE , який делегує видалення дітей до бази даних.

Приклад:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

        @ManyToOne
        @OnDelete(action = OnDeleteAction.CASCADE)
        private Parent parent;
}

За допомогою цього рішення достатньо однонаправлених відносин від дитини до батька, щоб автоматично видалити всіх дітей. Це рішення не потребує слухачів тощо. Також запит на зразок ВИДАЛИТИ З батьків, де id = 1 видалить дітей.


4
Я не можу змусити його працювати таким чином, чи є якась конкретна версія сплячки чи інший більш детальний приклад на зразок цього?
Мардарі

3
Важко сказати, чому це не працює для вас. Щоб зробити це робочим, вам може знадобитися відновити схему або вам доведеться додати видалення каскаду вручну. Анотація @OnDelete, здається, існує деякий час, тому я не здогадуюсь, що версія є проблемою.
Thomas Hunziker

10
Дякую за відповідь. Коротке зауваження: каскад тригера бази даних буде створений лише в тому випадку, якщо ви ввімкнули генерацію DDL через сплячий режим. Інакше вам доведеться додати його іншим способом (наприклад, liquidibase), щоб дозволити спеціальні запити виконуватись безпосередньо з БД, як-от "DELETE FROM Parent WHERE id = 1" виконувати каскадне видалення.
mjj1409

1
це не працює, коли асоціація є. @OneToOneБудь-які ідеї, як це вирішити @OneToOne?
stakowerflol

1
@ThomasHunziker це не спрацює з сиротою, чи не так?
оксит

13

Створіть двонаправлені відносини, наприклад:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}

8
погана відповідь, двонаправлені відносини страшні в JPA, оскільки операція на великих дитячих наборах займає неймовірну кількість часу
Enerccio

1
Чи є докази того, що двосторонні відносини повільні?
шалама

@enerccio Що робити, якщо двонаправлені відносини співвідносяться один до одного? Також, будь ласка, покажіть статтю, де сказано, що двонаправлені відносини є повільними? повільно в чому? отримання? видалення? оновлення?
saran3h

@ saran3h кожна операція (додавання, видалення) буде завантажувати всіх дітей, так що величезна навантаження даних може бути марною (наприклад, додавання значення не вимагає завантаження всіх дітей із бази даних, саме це робить відображення).
Enerccio

@Enerccio Я думаю, що всі використовують ліниве завантаження приєднання. То як це все-таки питання про продуктивність?
saran3h

1

Я бачив у однонаправленому @ManytoOne, видалення не працює так, як очікувалося. Коли батько видалено, в ідеалі дитина також повинна бути видалена, але видаляється лише батько, а дитина НЕ видаляється і залишається сиротою

Використовувані технології - Spring Boot / Spring Data JPA / Hibernate

Спринтовий завантаження: 2.1.2

Spring Data JPA / Hibernate використовується для видалення рядка .eg

parentRepository.delete(parent)

ParentRepository розширює стандартне сховище CRUD, як показано нижче ParentRepository extends CrudRepository<T, ID>

Далі йде мій предметний клас

@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;


}

Я знайшов рішення, чому видалення не працює. Мабуть, сплячий НЕ використовував mysql Engine -INNODB, вам потрібен двигун INNODB для mysql для створення зовнішнього ключа. Використовуючи наступні властивості у application.properties, змушує весняний завантажувач / сплячий режим використовувати движок mysql INNODB. Так обмеження закордонних ключів працює, а отже, і каскад видалення
ranjesh

Використання пропущених властивостей у попередньому коментарі. spring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
Далі

FYI, ви неправі "в коді. Дивітьсяname= "parent"
Олександр

0

Використовуйте цей спосіб, щоб видалити лише одну сторону

    @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;

-1

@Cascade (org.hibernate.annotations.CascadeType.DELETE_ORPHAN)

Дана анотація працювала на мене. Можна спробувати

Наприклад :-

     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;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.