javapostgresqlhibernatequarkusswagger-ui

Quarkus Swagger UI Hibernate Delete Request to PostgreDB fail silently


I have a Quarkus project using PostgreSQL and SwaggerUI, in which I am able to manually delete rows from tables in the IntelliJ DB UI, but when I try to do it with a DELETE request it fails without throwing any exception or error code. The strange thing is that if the row I am trying to delete is not connected by any FK to another table, the request actually goes through and deletes the row. That however, should not be a problem even if there are some FKs, since I can do it manually. I have also tried to DELETE request with Postman, and got the same result- failing silently. I will provide my entity, service, repository and resource classes. Just to be clear, there are 4 FK fields in the GameEntity, but I will provide only one of them (Genres) because they all behave the same. I am trying to delete from the Games table.

EDIT: I looked at my hibernate logs and it seems it is making a lot of selects but at the end it does not do a delete. Also, as stated, when the entity has no connection to another one, it gets deleted and the delete clause can be seen in the logs.

Deleting an entity with no connections:

[Hibernate] 
    select
        de1_0.id,
        de1_0.developer,
        de1_0.email 
    from
        Developers de1_0
[Hibernate] 
    select
        g1_0.developerID,
        g1_0.id,
        g1_0.description,
        g1_0.genreID,
        g2_0.id,
        g2_0.genre,
        g1_0.platformID,
        p1_0.id,
        p1_0.platform,
        g1_0.publisherID,
        p2_0.id,
        p2_0.email,
        p2_0.publisher,
        g1_0.release_date,
        g1_0.title 
    from
        Games g1_0 
    left join
        Genres g2_0 
            on g2_0.id=g1_0.genreID 
    left join
        Platforms p1_0 
            on p1_0.id=g1_0.platformID 
    left join
        Publishers p2_0 
            on p2_0.id=g1_0.publisherID 
    where
        g1_0.developerID = any (?)
[Hibernate] 
    select
        de1_0.id,
        de1_0.developer,
        de1_0.email,
        g1_0.developerID,
        g1_0.id,
        g1_0.description,
        g1_0.genreID,
        g2_0.id,
        g2_0.genre,
        g1_0.platformID,
        p1_0.id,
        p1_0.platform,
        g1_0.publisherID,
        p2_0.id,
        p2_0.email,
        p2_0.publisher,
        g1_0.release_date,
        g1_0.title 
    from
        Developers de1_0 
    left join
        Games g1_0 
            on de1_0.id=g1_0.developerID 
    left join
        Genres g2_0 
            on g2_0.id=g1_0.genreID 
    left join
        Platforms p1_0 
            on p1_0.id=g1_0.platformID 
    left join
        Publishers p2_0 
            on p2_0.id=g1_0.publisherID 
    where
        de1_0.id=?
[Hibernate] 
    delete 
    from
        Developers 
    where
        id=?

Deleting an entity with connections:

[Hibernate] 
    select
        ge1_0.id,
        ge1_0.description,
        ge1_0.developerID,
        d1_0.id,
        d1_0.developer,
        d1_0.email,
        ge1_0.genreID,
        g1_0.id,
        g1_0.genre,
        ge1_0.platformID,
        p1_0.id,
        p1_0.platform,
        ge1_0.publisherID,
        p2_0.id,
        p2_0.email,
        p2_0.publisher,
        ge1_0.release_date,
        ge1_0.title,
        r1_0.gameID,
        r1_0.id,
        r1_0.comment,
        r1_0.rating,
        r1_0.userID,
        u1_0.id,
        u1_0.email,
        u1_0.username 
    from
        Games ge1_0 
    join
        Developers d1_0 
            on d1_0.id=ge1_0.developerID 
    join
        Genres g1_0 
            on g1_0.id=ge1_0.genreID 
    join
        Platforms p1_0 
            on p1_0.id=ge1_0.platformID 
    join
        Publishers p2_0 
            on p2_0.id=ge1_0.publisherID 
    left join
        Reviews r1_0 
            on ge1_0.id=r1_0.gameID 
    left join
        Users u1_0 
            on u1_0.id=r1_0.userID 
    where
        ge1_0.id=?
[Hibernate] 
    select
        r1_0.userID,
        r1_0.id,
        r1_0.comment,
        r1_0.gameID,
        g1_0.id,
        g1_0.description,
        g1_0.developerID,
        d1_0.id,
        d1_0.developer,
        d1_0.email,
        g1_0.genreID,
        g2_0.id,
        g2_0.genre,
        g1_0.platformID,
        p1_0.id,
        p1_0.platform,
        g1_0.publisherID,
        p2_0.id,
        p2_0.email,
        p2_0.publisher,
        g1_0.release_date,
        g1_0.title,
        r1_0.rating 
    from
        Reviews r1_0 
    left join
        Games g1_0 
            on g1_0.id=r1_0.gameID 
    left join
        Developers d1_0 
            on d1_0.id=g1_0.developerID 
    left join
        Genres g2_0 
            on g2_0.id=g1_0.genreID 
    left join
        Platforms p1_0 
            on p1_0.id=g1_0.platformID 
    left join
        Publishers p2_0 
            on p2_0.id=g1_0.publisherID 
    where
        r1_0.userID=?
[Hibernate] 
    select
        g1_0.publisherID,
        g1_0.id,
        g1_0.description,
        g1_0.developerID,
        d1_0.id,
        d1_0.developer,
        d1_0.email,
        g1_0.genreID,
        g2_0.id,
        g2_0.genre,
        g1_0.platformID,
        p1_0.id,
        p1_0.platform,
        g1_0.release_date,
        g1_0.title 
    from
        Games g1_0 
    left join
        Developers d1_0 
            on d1_0.id=g1_0.developerID 
    left join
        Genres g2_0 
            on g2_0.id=g1_0.genreID 
    left join
        Platforms p1_0 
            on p1_0.id=g1_0.platformID 
    where
        g1_0.publisherID=?
[Hibernate] 
    select
        g1_0.platformID,
        g1_0.id,
        g1_0.description,
        g1_0.developerID,
        d1_0.id,
        d1_0.developer,
        d1_0.email,
        g1_0.genreID,
        g2_0.id,
        g2_0.genre,
        g1_0.publisherID,
        p2_0.id,
        p2_0.email,
        p2_0.publisher,
        g1_0.release_date,
        g1_0.title 
    from
        Games g1_0 
    left join
        Developers d1_0 
            on d1_0.id=g1_0.developerID 
    left join
        Genres g2_0 
            on g2_0.id=g1_0.genreID 
    left join
        Publishers p2_0 
            on p2_0.id=g1_0.publisherID 
    where
        g1_0.platformID=?
[Hibernate] 
    select
        g1_0.genreID,
        g1_0.id,
        g1_0.description,
        g1_0.developerID,
        d1_0.id,
        d1_0.developer,
        d1_0.email,
        g1_0.platformID,
        p1_0.id,
        p1_0.platform,
        g1_0.publisherID,
        p2_0.id,
        p2_0.email,
        p2_0.publisher,
        g1_0.release_date,
        g1_0.title 
    from
        Games g1_0 
    left join
        Developers d1_0 
            on d1_0.id=g1_0.developerID 
    left join
        Platforms p1_0 
            on p1_0.id=g1_0.platformID 
    left join
        Publishers p2_0 
            on p2_0.id=g1_0.publisherID 
    where
        g1_0.genreID=?
[Hibernate] 
    select
        g1_0.developerID,
        g1_0.id,
        g1_0.description,
        g1_0.genreID,
        g2_0.id,
        g2_0.genre,
        g1_0.platformID,
        p1_0.id,
        p1_0.platform,
        g1_0.publisherID,
        p2_0.id,
        p2_0.email,
        p2_0.publisher,
        g1_0.release_date,
        g1_0.title 
    from
        Games g1_0 
    left join
        Genres g2_0 
            on g2_0.id=g1_0.genreID 
    left join
        Platforms p1_0 
            on p1_0.id=g1_0.platformID 
    left join
        Publishers p2_0 
            on p2_0.id=g1_0.publisherID 
    where
        g1_0.developerID=?
@Entity(name = "Games")
public class GameEntity extends PanacheEntity {
    @Column(length = 100, nullable = false, unique = true)
    @NotBlank(message = "Title is required")
    private String title;

    @Column(length = 1000)
    private String description;

    @Column(name = "release_date")
    @Past(message = "Release date must be in the past or present")
    private LocalDate releaseDate;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn(name = "genreID", nullable = false)
    @NotNull(message = "Genre is required")
    private GenreEntity genre;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn(name = "publisherID", nullable = false)
    @NotNull(message = "Publisher is required")
    private PublisherEntity publisher;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn(name = "developerID", nullable = false)
    @NotNull(message = "Developer is required")
    private DeveloperEntity developer;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn(name = "platformID", nullable = false)
    @NotNull(message = "Platform is required")
    private PlatformEntity platform;

    @OneToMany(mappedBy = "game", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ReviewEntity> reviews;
    
    /// getters, setters, toString
}
@Entity(name = "Genres")
public class GenreEntity extends PanacheEntity {
    @Column(length = 50, nullable = false, unique = true)
    @NotBlank(message = "Genre name is required")
    private String genre;

    @OneToMany(mappedBy = "genre", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<GameEntity> games;

    /// getters, setters, toString
}
@ApplicationScoped
public class GameService {
    @Inject
    GameRepository repository;
    @Inject
    GenreRepository genreRepository;
    @Inject
    PublisherRepository publisherRepository;
    @Inject
    DeveloperRepository developerRepository;
    @Inject
    PlatformRepository platformRepository;


    public List<GameDTO> getAll() {
        return repository.listAll()  // Optional sorting
                .stream()
                .map(this::mapToDto)
                .toList();
    }

    @Transactional
    public void create(GameDTO dto) {
        GameEntity entity = mapToEntity(dto);
        repository.persist(entity);
    }

    @Transactional
    public void update(Long id, GameDTO dto) {
        GameEntity updatedEntity = mapToEntity(dto);
        GameEntity entity = repository.findById(id);
        if (entity == null)
            throw new NotFoundException("Game not found with id: " + id);

        entity.setTitle(updatedEntity.getTitle());
        entity.setDescription(updatedEntity.getDescription());
        entity.setReleaseDate(updatedEntity.getReleaseDate());
        entity.setGenre(updatedEntity.getGenre());
        entity.setPublisher(updatedEntity.getPublisher());
        entity.setDeveloper(updatedEntity.getDeveloper());
        entity.setPlatform(updatedEntity.getPlatform());

        repository.persist(entity);
    }

    @Transactional
    public void delete(Long id) {
        GameEntity entity = repository.findById(id);
        if (entity == null)
            throw new NotFoundException("Game not found with id: " + id);

        repository.delete(entity);
    }

    private GameEntity mapToEntity(GameDTO dto) {
        GenreEntity genre = genreRepository.findByGenre(dto.getGenre());
        if (genre == null)
            throw new NotFoundException("Genre not found.");
        PublisherEntity publisher = publisherRepository.findByPublisher(dto.getPublisher());
        if (publisher == null)
            throw new NotFoundException("Publisher not found.");
        DeveloperEntity developer = developerRepository.findByDeveloper(dto.getDeveloper());
        if (developer == null)
            throw new NotFoundException("Developer not found.");
        PlatformEntity platform = platformRepository.findByPlatform(dto.getPlatform());
        if (platform == null)
            throw new NotFoundException("Platform not found.");

        GameEntity entity = new GameEntity();
        entity.setTitle(dto.getTitle());
        entity.setDescription(dto.getDescription());
        entity.setReleaseDate(dto.getReleaseDate());
        entity.setGenre(genre);
        entity.setPublisher(publisher);
        entity.setDeveloper(developer);
        entity.setPlatform(platform);
        return entity;
    }

    private GameDTO mapToDto(GameEntity e) {
        return new GameDTO(
                e.getID(),
                e.getTitle(),
                e.getDescription(),
                e.getReleaseDate(),
                e.getGenre().getGenre(),
                e.getPublisher().getPublisher(),
                e.getDeveloper().getDeveloper(),
                e.getPlatform().getPlatform()
        );
    }
}
@ApplicationScoped
@Path("api/v1/games")
public class GameResource {

    @Inject
    GameService service;

    @GET
    public Response getAll() {
        List<GameDTO> list = service.getAll();
        return Response
                .ok(list)
                .build();
    }

    @POST
    public Response create(GameDTO dto) {
        service.create(dto);
        return Response
                .status(Response.Status.CREATED)
                .entity(dto)
                .build();
    }

    @PUT
    @Path("/{id}")
    public Response update(@PathParam("id") Long id, GameDTO dto) {
        service.update(id, dto);
        return Response
                .ok(dto)
                .build();
    }

    @DELETE
    @Path("/{id}")
    public Response delete(@PathParam("id") Long id) {
        service.delete(id);
        return Response
                .noContent()
                .build();
    }
}

Solution

  • The solution was using a native query, which bypasses all of the loading that hibernate does and directly executes the delete command:

    @Transactional
        public void delete(Long id) {
            long deleted = repository.delete("id", id);
            if (deleted == 0) {
                throw new NotFoundException("Game not found with id: " + id);
            }
        }