Я столкнулся с ошибкой с пользовательским методом удаления в весенних данных 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())
, все в порядке.
Я хотел бы знать, что не так с настройкой этого метода удаления и как это исправить.
В случае удаления сущности через 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
Я думаю, что в вашем случае лучшим будет Решение 3. Удаление с помощью собственного запроса полезно только тогда, когда вы определили огромный набор записей для операции удаления, потому что собственный запрос имеет лучшую производительность. Поэтому, если установлены огромные рекорды, используйте Решение 1.
Спасибо за подробное объяснение! Еще вопрос: какое вообще лучшее из 4-х решений?