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