JPA: однонаправленное много-к-одному и каскадное удаление

98

Скажем, у меня однонаправленные @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
Даже если только «Дочерний» имеет ссылку на «Родителя» (таким образом, ссылка является однонаправленной), для вас проблематично добавить список «Дочерний» с сопоставлением «@OneToMany» и атрибутом «Cascade = ALL» в родитель'? Я предполагаю, что JPA должна решить, что даже в жестких условиях ссылка может быть на одной стороне.
kvDennis
1
@kvDennis, бывают случаи, когда вы не хотите плотно связывать многостороннее с одной стороной. Например, в ACL-подобных настройках, где разрешения безопасности являются прозрачными «надстройками»
Бачи

Ответы:

73

Отношения в JPA всегда однонаправлены, если вы не связываете родительский элемент с дочерним в обоих направлениях. Каскадные операции REMOVE от родителя к потомку потребуют отношения от родителя к потомку (а не только наоборот).

Поэтому вам необходимо сделать это:

  • Либо измените однонаправленную @ManyToOneсвязь на двунаправленную @ManyToOne, либо на однонаправленную @OneToMany. Затем вы можете каскадировать операции REMOVE, чтобы EntityManager.removeудалить родительский элемент и дочерние элементы. Вы также можете указать значение orphanRemovaltrue для удаления любых потерянных дочерних элементов, когда дочерний объект в родительской коллекции имеет значение null, то есть удалить дочерний элемент, если он отсутствует в родительской коллекции.
  • Или укажите ограничение внешнего ключа в дочерней таблице как ON DELETE CASCADE. Вам нужно будет вызвать EntityManager.clear()после вызова, EntityManager.remove(parent)поскольку необходимо обновить контекст постоянства - дочерние сущности не должны существовать в контексте постоянства после того, как они были удалены в базе данных.
Винит Рейнольдс
источник
7
есть ли способ сделать №2 с аннотацией JPA?
user2573153
3
Как мне сделать No2 с сопоставлениями XML Hibernate?
arg20
94

Если вы используете hibernate в качестве провайдера 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;
}

С этим решением однонаправленных отношений от дочернего к родительскому достаточно для автоматического удаления всех дочерних элементов. Это решение не требует никаких слушателей и т. Д. Также запрос типа DELETE FROM Parent WHERE id = 1 удалит потомков.

Томас Ханзикер
источник
4
Я не могу заставить его работать таким образом, есть ли какая-то конкретная версия спящего режима или другой более подробный пример, подобный этому?
Мардарь
3
Сложно сказать, почему у вас не работает. Чтобы это работало, вам может потребоваться регенерировать схему или вручную добавить каскадное удаление. Аннотация @OnDelete, кажется, существует какое-то время, поэтому я бы не догадался, что версия является проблемой.
Thomas Hunziker
12
Спасибо за ответ. Краткое примечание: триггер каскада базы данных будет создан только в том случае, если вы включили генерацию DDL через спящий режим. В противном случае вам придется добавить другой способ (например, Liquibase), чтобы разрешить специальные запросы, запускаемые непосредственно в БД, например, «УДАЛИТЬ ИЗ Parent WHERE id = 1», выполнить каскадное удаление.
mjj1409
1
это не работает, когда ассоциация есть. @OneToOneЕсть идеи, как это решить @OneToOne?
stakowerflol
1
@ThomasHunziker, это не сработает для orphanRemoval, верно?
oxyt 05
14

Создайте двунаправленные отношения, например:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}
Текумара
источник
9
плохой ответ, двунаправленные отношения ужасны в JPA, потому что работа с большими детскими наборами занимает невероятное количество времени
Enerccio
1
Есть ли доказательства того, что двунаправленные отношения работают медленно?
shalama
@enerccio Что, если двунаправленная связь однозначна? Также, пожалуйста, покажите статью, в которой говорится, что двусторонние отношения являются медленными? медленный в чем? получение? удаление? обновление?
saran3h
@ saran3h каждая операция (добавление, удаление) будет загружать все дочерние элементы, так что это огромная нагрузка данных, которая может быть бесполезной (например, добавление значения не требует загрузки всех дочерних элементов из базы данных, что и делает это сопоставление).
Enerccio
@Enerccio Я думаю, что все используют ленивую загрузку соединений. Так почему же это все еще проблема производительности?
saran3h
2

Я видел в однонаправленном @ManytoOne, удаление не работает должным образом. Когда родитель удаляется, в идеале дочерний элемент также должен быть удален, но удаляется только родительский элемент, а дочерний элемент НЕ удаляется и остается сиротой.

Используемые технологии: Spring Boot / Spring Data JPA / Hibernate.

Загрузка Sprint: 2.1.2.RELEASE

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 заставляет spring boot / hibernate использовать механизм mysql INNODB. Таким образом, ограничение внешнего ключа работает и, следовательно, также удаляет каскад
Ранжеш
Упущенные свойства используются в предыдущем комментарии. Ниже приведены используемые свойства пружиныspring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
Ранжеш
К вашему сведению, вы ошиблись "в коде. См.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;
    }
Сварит Агарвал
источник
-1

Вам не нужно использовать двунаправленную ассоциацию вместо вашего кода, вам просто нужно добавить CascaType.Remove в качестве свойства к аннотации ManyToOne, а затем использовать @OnDelete (action = OnDeleteAction.CASCADE), у меня это отлично работает.

Чеди
источник