Сбой каскадного удаления JPA с помощью пользовательского метода удаления

Я столкнулся с ошибкой с пользовательским методом удаления в весенних данных jpa. По сути, есть сумка, в которой есть предметы, и при удалении сумки все предметы в ней должны быть удалены.

Вот сущности:

@Entity
@Table(name = "bag")
public class Bag {
    @Id private Long id;
    @Column("uid") private Long uid;
    @Column("name") private String name;

    @OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Item> items;
}

@Entity
@Table(name = "item")
public class Item {
    @Id private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "bid", referencedColumnName = "id")
    private Bag bag;
}

и репозиторий:

@Repository
public interface BagRepository extends JpaRepository<Bag, Long> {
    Bag findByUidAndName(Long uid, String name);

    @Transactional
    @Modifying
    @Query(value = "DELETE FROM `bag` WHERE `uid` = :uid AND `name` = :name", nativeQuery = true)
    void deleteByUidAndName(@Param("uid") Long uid, @Param("name") String name);
}

Когда я вызываю bagRepository.deleteByUidAndName(uid, name), я получаю исключение из режима гибернации, связанное с ограничением внешнего ключа. Параметр spring.jpa.show-sql=true показывает, что он не пытается удалить элементы перед удалением сумки.

Однако, если я позвоню Bag bag = bagRepository.findByUidAndName(uid, name), а затем bagRepository.deleteById(bag.getId()), все в порядке.

Я хотел бы знать, что не так с настройкой этого метода удаления и как это исправить.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
38
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В случае удаления сущности через bagRepository.deleteById(bag.getId()) Hibernate будет удалена из родительской сущности в дочернюю, поскольку вы определили каскад = CascadeType.ALL для отношения. Когда мы выполняем какое-либо действие над целевым объектом, то же самое действие будет применено к связанному объекту. Логика находится в спящем режиме и не использует каскады баз данных.

    @OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Item> items;

В случае bagRepository.deleteByUidAndName(uid, name) вы определили собственный запрос на удаление. Это означает, что логика Hibernate будет проигнорирована, а запрос будет выполняться как есть. В этом случае вы работаете напрямую с базой данных, и для удаления записи через собственный SQL вам нужно определить ON DELETE CASCADE на уровне базы данных, чтобы иметь аналогичную логику.

    @Query(value = "DELETE FROM `bag` WHERE `uid` = :uid AND `name` = :name", nativeQuery = true)
    void deleteByUidAndName(@Param("uid") Long uid, @Param("name") String name);

Решение 1, @OnDelete(действие = OnDeleteAction.CASCADE)

Если у вас есть автоматически сгенерированные таблицы, вы можете добавить к отношению специфичную для Hibernate аннотацию @OnDelete. Во время генерации таблиц ON DELETE CASCADE будет применяться к ограничению внешнего ключа.
Определение отношения:

    @OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private List<Item> items;

Автоматически сгенерированная константа:

    alter table item 
       add constraint FK19sn210fxmx43i8r3icevbeup 
       foreign key (bid) 
       references bag 
       on delete cascade

Реализация:

import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "bag")
public class Bag {
    @Id
    private Long id;
    @Column(name = "uid")
    private Long uid;
    @Column(name = "name")
    private String name;

    @OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private List<Item> items;
}

Решение 2, аннотация @JoinColumn с внешним ключом ON DELETE CASCADE
Укажите внешний ключ с ON DELETE CASCADE для Item объекта

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "bid", referencedColumnName = "id",
            foreignKey = @ForeignKey(
                    name = "FK_ITEMS_ID",
                    foreignKeyDefinition = "FOREIGN KEY (ID) REFERENCES ITEM(BID) ON DELETE CASCADE"))
    private Bag bag;

Реализация:

import javax.persistence.*;

@Entity
@Table(name = "item")
public class Item {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "bid", referencedColumnName = "id",
            foreignKey = @ForeignKey(
                    name = "FK_ITEMS_ID",
                    foreignKeyDefinition = "FOREIGN KEY (ID) REFERENCES ITEM(BID) ON DELETE CASCADE"))
    private Bag bag;
}

Решение 3, не используйте собственный запрос
В этом случае будет применена логика Hibernate.
Определите репозиторий, например:

@Repository
public interface BagRepository extends JpaRepository<Bag, Long> {
    Bag findByUidAndName(Long uid, String name);

    @Transactional
    @Modifying   
    void deleteByUidAndName(@Param("uid") Long uid, @Param("name") String name);
}

Решение 4. Добавьте в базу данных ON DELETE CASCADE вручную.
Если ваша таблица не генерируется автоматически, вы можете вручную добавить ON DELETE CASCADE в базу данных.

    alter table item 
       add constraint FK_BAG_BID
       foreign key (bid) 
       references bag 
       on delete cascade

Спасибо за подробное объяснение! Еще вопрос: какое вообще лучшее из 4-х решений?

Nicolás_Tsu 07.05.2022 08:57

Я думаю, что в вашем случае лучшим будет Решение 3. Удаление с помощью собственного запроса полезно только тогда, когда вы определили огромный набор записей для операции удаления, потому что собственный запрос имеет лучшую производительность. Поэтому, если установлены огромные рекорды, используйте Решение 1.

Eugene 07.05.2022 09:24

Другие вопросы по теме