Порядок выполнения гибернации с двунаправленными ссылками - PullRequest
0 голосов
/ 07 мая 2020

Установка

Моя среда - 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)) до того, как он запустит команду удаления?

1 Ответ

1 голос
/ 11 мая 2020

Вы хотите установить идентификатор ребенка на null, но вы запретили изменение столбца:

@JoinColumn(name = "FAVOURITE_CHILD_ID", insertable = false, updatable = false)
private Child favourite;

Просто удалите updatable = false.

...