How to continue transaction in Spring Boot with PostgreSQL after an exception is thrown?
I created a service method that creates user accounts. If creation fails because the given email is already in our database, I want to send the user an email with which they are already registered:
@Transactional(noRollbackFor=DuplicateEmailException.class)
void registerUser(User user) {
try {
userRepository.create(user);
catch(DuplicateEmailException e) {
User registeredUser = userRepository.findByEmail(user.getEmail());
mailService.sendAlreadyRegisteredEmail(registeredUser);
}
}
This does not work. Although I've marked it DuplicateEmailExcepetion
as "no rollback", the second SQL query (findByEmail) still fails because the transaction was aborted.
What am I doing wrong?
There is no annotation in the repository @Transactional
.
source to share
This is not a Spring / JDBC problem or your code, the problem is with the database. For example, when using Postgres, if any statement fails in a transaction, all subsequent statements will fail with current transaction is aborted
.
For example, do the following in Postgres:
> start a transaction
> DROP SEQUENCE BLA_BLA_BLA;
> Error while executing the query; ERROR: sequence "BLA_BLA_BLA" does not exist"
> SELECT * FROM USERS;
> ERROR: current transaction is aborted, commands ignored until end of transaction block
However, SELECT and subsequent statements are expected to be successful against MySQL, Oracle and SQL Server
source to share
Why don't you change your logic like this:
void registerUser(User user) {
User existingUser = userRepository.findByEmail(user.getEmail())
if(existingUser == null){
userRepository.create(user);
}else{
mailService.sendAlreadyRegisteredEmail(existingUser)
}
}
This will ensure that only non-existent users are included in the database.
source to share
@Transactional
the annotation is not positioned correctly. Spring creates an AOP advisor around the method where the annotation is defined @Transactional
. So in this case the pointcut will be created around the method registedUser
. But the method registerUser
doesn't throw DuplicateEmailException
. Therefore, rollback rules are not evaluated.
You need to define a rule @Transactional
around the method UserRepository.createUser
. This ensures that the transaction pointcut generated by Spring is not rolled back due to DuplicateEmailException
.
public class UserRepository {
@Transactional(noRollbackFor=DuplicateEmailException.class)
public User createUser(){
//if user exist, throw DuplicateEmailException
}
}
void registerUser(User user) {
try {
userRepository.create(user);
catch(DuplicateEmailException e) {
User registeredUser = userRepository.findByEmail(user.getEmail());
mailService.sendAlreadyRegisteredEmail(registeredUser);
}
}
source to share