javaspring-boothibernatejpah2

Referential integrity constraint violation error in Spring Boot using H2 database


I have a problem with testing entities in my simple Spring Boot library api.

In this test I even manually remove every book from titles to ensure it doesn't violate anything, but the problem still occurs in line 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();
    }

I receive this error message:

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=?]

Here is my book entity

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;
    }
}

And here is title entity, where I use Cascade.ALL, so when a title is removed all books should be removed as well.

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;
    }
}

For testing i use hibernate's H2 database with this testing profile in 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

Here are the full SQL logs from 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)
    )
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]

Solution

  • Ok the solution to the problem was simply to add on delete cascade to the constraint by adding @OnDelete(action = OnDeleteAction.CASCADE) to the @OneToMany relation in entity.

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

    So the constraint is being added like this:

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