I am sort of stuck at a particular funny error in a quarkus REST-service that I am currently developing.
My application manages panache entities and I implemented some CRUD operations in a resource controller.
I annotated that controller with @Transactional
(like the documentation suggest) and for good measure I added a global exception handler which looks roughly like the following snippet:
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
public class MyExceptionHandler {
@ServerExceptionMapper
public Response entityToBig(EntityToBigException e) {
return Response.status(Response.Status.BAD_REQUEST)
// ErrorMessage is just a record to hold the message
.entity(new ErrorMessage("Error", e.getMessage(), Response.Status.BAD_REQUEST.getStatusCode())).build();
}
//... other handlers
}
The thing is now, that if I throw a Exception which is handled by my ExceptionHandler class the Status code and message is presented correctly to the end-user but the changes done in the prior request are ALSO committed to the database!
If I throw a Exception that is not handled by my ExceptionHandler class the transaction rollback works just fine...
I could not find any other documentation on this issue so I am quite perplexed on how to solve this/manually tell the transaction manager to rollback my changes.
Ok so the fix is quite simple but very hidden away in the depths of the documentation.
From https://quarkus.io/guides/transaction#starting-and-stopping-transactions-defining-your-boundaries we can learn that only RuntimeException
s will automatically rollback the current transaction context.
So that means if I, who of course tries to develop clean software, define a custom base Exception class for my service which is just a regular checked exception, that exception will NEVER rollback the current Transaction.
The solution to this is pretty simple, take your class that is annotated with @Transactional
(as the documentation suggest a REST-Controller) and add the prameter rollbackOn
to the decorator.
@ApplicationScoped
@Path("/api/v1/")
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = "CustomerNetwork API", description = "Operations related to customer networks")
@Authenticated
@Transactional(rollbackOn = {EntityToBigException.class})
public class CustomerNetworkController {
// REST endpoints ...
}
This ensures that the Transaction is rolled back whenever your custom exception or exceptions that inherit your exception are thrown.