Установка
Моя среда - Spring Boot 2.1 с MS SQL Server и Hibernate. Моя установка содержит простую ассоциацию родитель-потомок со специальной функцией. У одного из родителей много детей. У родителя может быть любимый ребенок. Родитель содержит ссылку на любимого потомка.
У меня нет влияния на дизайн базы данных . Схема базы данных:
PARENT
------------------------------------------------------------------------------------
ID NOT NULL,
FAVOURITE_CHILD_ID NULL,
CONSTRAINT FK_PARENT_FAVOURITE_CHILD_ID FOREIGN KEY(FAVOURITE_CHILD_ID) ON CHILD(ID)
CHILD
------------------------------------------------------------------------------------
ID NOT NULL,
PARENT_ID NOT NULL,
CONSTRAINT FK_CHILD_PARENT_ID FOREIGN KEY(PARENT_ID) ON PARENT(ID)
Модели (упрощенный код):
@Entity
public class Parent {
@Id
@GeneratedValue(...)
@GenericGenerator(...)
@Column(name = "ID", updatable = false, nullable = false, unique = true)
private Long id;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
private Set<Child> children;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "FAVOURITE_CHILD_ID", insertable = false, updatable = false)
private Child favourite;
}
@Entity
public class Child {
@Id
@GeneratedValue(...)
@GenericGenerator(...)
@Column(name = "ID", updatable = false, nullable = false, unique = true)
private Long id;
@Column(name = "PARENT_ID")
@NotNull
private Long parentId;
@ManyToOne(optional = false)
@JoinColumn(name = "PARENT_ID", insertable = false, updatable = false, nullable = false)
private Parent parent;
}
Родитель настроен на каскадное удаление своих дочерних элементов.
Репозиторий:
public interface ParentRepository extends CrudRepository<Parent, Long> {
}
pom. xml имеет ссылку на:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
При включенном Spring Data REST приложение создает конечную точку HTTP, обслуживающую запросы для родителей.
Проблема
Я получаю сообщение об ошибке при удалении родителя, у которого есть любимый ребенок. Примеры данных содержат родительский номер 1, у которого нет любимого дочернего элемента, и родительский номер 2, у которого есть любимый дочерний элемент:
PARENT
ID FAVOURITE_CHILD_ID
-- ------------------
1 NULL
2 202
CHILD
ID PARENT_ID
--- ---------
101 1
102 1
201 2
202 2
Запрос DELETE
на http://localhost:8080/parents/1
выполняется нормально и удаляет первого родителя и его дочерние элементы.
Запрос DELETE
на http://localhost:8080/parents/2
вызывает исключение. SQL Сообщение сервера:
Оператор DELETE конфликтует с ограничением REFERENCE «FK_PARENT_FAVOURITE_CHILD_ID». Конфликт произошел в базе данных «test», таблица «dbo.PARENT», столбец «FAVOURITE_CHILD_ID».
Профилировщик показывает, что Hibernates сначала запускает оператор удаления для таблицы CHILD:
exec sp_executesql N'delete from child where id=@P0',N'@P0 bigint',2
go
Я бы ожидал, что Hibernate сначала удалит ссылку на любимого потомка, то есть на UPDATE PARENT SET FAVOURITE_CHILD_ID = NULL WHERE ID = 2
.
Но это не так. Есть ли способ настроить Hibernate, чтобы он мог решить эту проблему самостоятельно?
Попытка решения
Я бы предпочел решение по конфигурации. Поскольку я не смог найти ни одного, я попытался взять на себя больший контроль, переопределив конечную точку REST репозитория Spring с помощью настраиваемого контроллера:
@RepositoryRestController
public class RepositoryRestMethodOverrideController {
@DeleteMapping(path = "/parents/{parentId}")
@Transactional
public ResponseEntity<?> deleteParent(@PathVariable Long parentId) {
Parent parent = parentRepository.findById(parentId).orElseThrow(NotFoundException::new);
if (parent.getFavouriteChildId() != null) {
parent.setFavouriteChildId(null);
parent.setFavouriteChild(null);
parentRepository.save(parent);
}
parentRepository.delete(parent);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
К сожалению, я вижу те же результаты (сообщения об ошибках). Есть ли способ указать Hibernate записать обновление (parentRepository.save(parent)
) до того, как он запустит команду удаления?