Ошибка нарушения ограничения ссылочной целостности в Spring Boot с использованием базы данных H2

У меня проблема с тестированием объектов в моем простом API библиотеки Spring Boot.

В этом тесте я даже вручную удаляю названия каждой книги, чтобы убедиться, что она ничего не нарушает, но проблема все равно возникает в очереди entityManager.remove(title);

    @Test
    @Transactional
    void titleAssociationTest() {
        title.getBooks().forEach(book -> {
            if (book.getLoan() != null) {
                entityManager.remove(book.getLoan());
            }
            entityManager.remove(book);
        });
        title.getBooks().clear();

        entityManager.flush();

        entityManager.remove(title);
        entityManager.flush();

        Title foundTitle = entityManager.find(Title.class, title.getId());
        assertThat(foundTitle).isNull();
    }

Я получаю это сообщение об ошибке:

Referential integrity constraint violation: "FK6I4NJ1U4KX1J2YOVIIM8HT8S9: PUBLIC.BOOKS FOREIGN KEY(TITLE_ID) REFERENCES PUBLIC.TITLES(ID) (CAST(1 AS BIGINT))"; SQL statement: 
delete from titles where id=? [23503-214]] [delete from titles where id=?]

Вот моя книжная сущность

package com.projects.library.model;

import com.projects.library.enums.BookStatus;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@Entity
@Table(name = "books")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @ManyToOne
    @JoinColumn(name = "title_id", nullable = false)
    private Title title;

    @Enumerated(EnumType.STRING)
    @Column(name = "status", nullable = false)
    private BookStatus status;

    @OneToOne(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
    private Loan loan;

    public Book(Title title, BookStatus status) {
        this.title = title;
        this.status = status;
    }
}

А вот объект title, где я использую Cascade.ALL, поэтому при удалении заголовка следует удалить и все книги.

package com.projects.library.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.HashSet;
import java.util.Set;

@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@Entity
@Table(name = "titles")
public class Title {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "`title`", nullable = false, unique = true)
    private String title;

    @Column(name = "author", nullable = false)
    private String author;

    @Column(name = "`year`", nullable = false)
    private int year;

    @OneToMany(mappedBy = "title", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Book> books = new HashSet<>();

    public Title(String title, String author, int year) {
        this.title = title;
        this.author = author;
        this.year = year;
    }
}

Для тестирования я использую базу данных Hibernate H2 с этим профилем тестирования в application-test.properties

spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect

Вот полные журналы SQL из спящего режима:

    create table books (
        id bigint generated by default as identity,
        status varchar(255) not null check (status in ('AVAILABLE','RENTED')),
        title_id bigint not null,
        primary key (id)
    )
Hibernate: 
    create table books (
        id bigint generated by default as identity,
        status varchar(255) not null check (status in ('AVAILABLE','RENTED')),
        title_id bigint not null,
        primary key (id)
    )
2024-08-20T14:07:28.365+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    create table loans (
        id bigint generated by default as identity,
        borrow_date timestamp(6) not null,
        return_date timestamp(6),
        book_id bigint not null,
        user_id bigint not null,
        primary key (id)
    )
Hibernate: 
    create table loans (
        id bigint generated by default as identity,
        borrow_date timestamp(6) not null,
        return_date timestamp(6),
        book_id bigint not null,
        user_id bigint not null,
        primary key (id)
    )
2024-08-20T14:07:28.371+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    create table titles (
        id bigint generated by default as identity,
        author varchar(255) not null,
        "title" varchar(255) not null,
        "year" integer not null,
        primary key (id)
    )
Hibernate: 
    create table titles (
        id bigint generated by default as identity,
        author varchar(255) not null,
        "title" varchar(255) not null,
        "year" integer not null,
        primary key (id)
    )
2024-08-20T14:07:28.371+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    create table users (
        id bigint generated by default as identity,
        account_creation_date timestamp(6) not null,
        first_name varchar(255) not null,
        last_name varchar(255) not null,
        primary key (id)
    )
Hibernate: 
    create table users (
        id bigint generated by default as identity,
        account_creation_date timestamp(6) not null,
        first_name varchar(255) not null,
        last_name varchar(255) not null,
        primary key (id)
    )
2024-08-20T14:07:28.379+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    alter table if exists loans 
       drop constraint if exists UK_7t9p42ubdqp606tp4whc233lt
Hibernate: 
    alter table if exists loans 
       drop constraint if exists UK_7t9p42ubdqp606tp4whc233lt
2024-08-20T14:07:28.379+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    alter table if exists loans 
       add constraint UK_7t9p42ubdqp606tp4whc233lt unique (book_id)
Hibernate: 
    alter table if exists loans 
       add constraint UK_7t9p42ubdqp606tp4whc233lt unique (book_id)
2024-08-20T14:07:28.379+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    alter table if exists titles 
       drop constraint if exists UK_teg25db4y95gscy7bdb9jqmes
Hibernate: 
    alter table if exists titles 
       drop constraint if exists UK_teg25db4y95gscy7bdb9jqmes
2024-08-20T14:07:28.379+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    alter table if exists titles 
       add constraint UK_teg25db4y95gscy7bdb9jqmes unique ("title")
Hibernate: 
    alter table if exists titles 
       add constraint UK_teg25db4y95gscy7bdb9jqmes unique ("title")
2024-08-20T14:07:28.379+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    alter table if exists books 
       add constraint FK6i4nj1u4kx1j2yoviim8ht8s9 
       foreign key (title_id) 
       references titles
Hibernate: 
    alter table if exists books 
       add constraint FK6i4nj1u4kx1j2yoviim8ht8s9 
       foreign key (title_id) 
       references titles
2024-08-20T14:07:28.389+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    alter table if exists loans 
       add constraint FKokwvlrv6o4i4h3le3bwhe6kie 
       foreign key (book_id) 
       references books
Hibernate: 
    alter table if exists loans 
       add constraint FKokwvlrv6o4i4h3le3bwhe6kie 
       foreign key (book_id) 
       references books
2024-08-20T14:07:28.389+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    alter table if exists loans 
       add constraint FK6xxlcjc0rqtn5nq28vjnx5t9d 
       foreign key (user_id) 
       references users
Hibernate: 
    alter table if exists loans 
       add constraint FK6xxlcjc0rqtn5nq28vjnx5t9d 
       foreign key (user_id) 
       references users
2024-08-20T14:07:28.389+02:00  INFO 12728 --- [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-08-20T14:07:28.780+02:00  INFO 12728 --- [    Test worker] o.s.d.j.r.query.QueryEnhancerFactory     : Hibernate is in classpath; If applicable, HQL parser will be used.
2024-08-20T14:07:29.384+02:00  INFO 12728 --- [    Test worker] c.projects.library.model.BookTestSuite   : Started BookTestSuite in 3.519 seconds (process running for 4.796)
WARNING: A Java agent has been loaded dynamically (C:\Users\Pawe?\.gradle\caches\modules-2\files-2.1\net.bytebuddy\byte-buddy-agent\1.14.13\979ce25f7d3096a2e82214ba7dc972a05ce7a171\byte-buddy-agent-1.14.13.ja)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
2024-08-20T14:07:29.884+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    insert 
    into
        titles
        (author,"title","year",id) 
    values
        (?,?,?,default)
Hibernate: 
    insert 
    into
        titles
        (author,"title","year",id) 
    values
        (?,?,?,default)
2024-08-20T14:07:29.899+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    insert 
    into
        users
        (account_creation_date,first_name,last_name,id) 
    values
        (?,?,?,default)
Hibernate: 
    insert 
    into
        users
        (account_creation_date,first_name,last_name,id) 
    values
        (?,?,?,default)
2024-08-20T14:07:29.903+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    insert 
    into
        books
        (status,title_id,id) 
    values
        (?,?,default)
Hibernate: 
    insert 
    into
        books
        (status,title_id,id) 
    values
        (?,?,default)
2024-08-20T14:07:29.906+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    insert 
    into
        loans
        (book_id,borrow_date,return_date,user_id,id) 
    values
        (?,?,?,?,default)
Hibernate: 
    insert 
    into
        loans
        (book_id,borrow_date,return_date,user_id,id) 
    values
        (?,?,?,?,default)
2024-08-20T14:07:29.924+02:00 DEBUG 12728 --- [    Test worker] org.hibernate.SQL                        : 
    delete 
    from
        titles 
    where
        id=?
Hibernate: 
    delete 
    from
        titles 
    where
        id=?
2024-08-20T14:07:29.931+02:00  WARN 12728 --- [    Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23503, SQLState: 23503
2024-08-20T14:07:29.931+02:00 ERROR 12728 --- [    Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper   : Naruszenie więzów integralności: "FK6I4NJ1U4KX1J2YOVIIM8HT8S9: PUBLIC.BOOKS FOREIGN KEY(TITLE_ID) REFERENCES PUBLIC.TITLES(ID) (CAST(1 AS BIGINT))"
Referential integrity constraint violation: "FK6I4NJ1U4KX1J2YOVIIM8HT8S9: PUBLIC.BOOKS FOREIGN KEY(TITLE_ID) REFERENCES PUBLIC.TITLES(ID) (CAST(1 AS BIGINT))"; SQL statement:
delete from titles where id=? [23503-214]
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваше ограничение FK6I4NJ1U4KX1J2YOVIIM8HT8S9 было создано следующим образом:

    alter table if exists books 
       add constraint FK6i4nj1u4kx1j2yoviim8ht8s9 
       foreign key (title_id) 
       references titles

Итак, у вас есть таблица books, в которой есть title_id, который является ссылкой foreign key на titles.

Итак, вот в чем дело: всякий раз, когда вы намереваетесь удалить заголовок, все книги, ссылающиеся на него, становятся висящими, а ограничение не имеет on delete cascade, поэтому оно не будет очищено, оставив вас с нарушением целостности.

Бэльдунг предполагает, что это может помочь:

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)

и у тебя есть

@OneToMany(mappedBy = "title", cascade = CascadeType.ALL, orphanRemoval = true)

Я не думаю, что ваш mappedBy может быть виноват, но и этого исключать не могу. Тем не менее убедитесь, что схема вашей базы данных синхронизирована с аннотацией. Поэтому убедитесь, что ваша схема обновлена.

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

Хорошо, решением проблемы было просто добавить on delete cascade к ограничению, добавив @OnDelete(action = OnDeleteAction.CASCADE) к отношению @OneToMany в сущности.

    @OneToMany(mappedBy = "title", cascade = CascadeType.REMOVE, orphanRemoval = true)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Set<Book> books = new HashSet<>();

Таким образом, ограничение добавляется следующим образом:

    alter table if exists books 
       add constraint FK6i4nj1u4kx1j2yoviim8ht8s9 
       foreign key (title_id) 
       references titles 
       on delete cascade

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