Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catch DataIntegrityViolationException in @Transactional service method

I have a REST Spring boot app with Hibernate. For simplicity let's assume this workflow:

  1. Controller handles incoming requests, calls Service methods
  2. Service methods are @Transactional, do some business logic and call Persistence methods
  3. Persistence methods are handled by DAO objects, saving stuff into database.

The database has a unique constraint on username of a User. The way I have it working now is this:

  1. Client sends request to Controller
  2. Controller calls Service
  3. Service attempts to save an object through DAO. If DataViolationException occurs, Service returns a custom Exception
  4. Controller catches the custom Exception and sends back appropriate response

The pseudocode is this:

public class UserController {
    @RequestMapping("/user")
    public User createUser(...){
        try{
            return userService.createUser(...);
        } catch (UserAlreadyExistsException e){
            // Do some processing and return error message to client
        }
    }
}

public class UserService {
    @Transactional
    public User createUser(...){
        (...)
        try{
            userDAO.save(newUserObject);
        } catch(DataIntegrityViolationException e){
            throw new UserAlreadyExistsException(username);
        }
    }
}

However, this way I am getting an error when a duplicate user is attempted to be created.

javax.persistence.RollbackException: Transaction marked as rollbackOnly

One way to fix this seems to be to let the DataIntegrityViolationException "bubble" up from the transaction (and not catch it in Service). But that means that the Controller has to handle persistence exceptions and I don't like that.

I prefer if the service threw "understandable" exceptions for the Controller to handle. The service knows what persistence exceptions to expect and when and is able to "translate" the broad DataIntegrityViolationException into a meaningful one.

Is there a way to handle the exceptions this way? I don't particularly like the idea of having a "2-layered service layer" to achieve this.


EDIT: Another reason I want to throw my custom Exception is that it is required by the compiler to be caught. I want to enforce the controller to handle all possible exceptions that may occur.

like image 632
Martin Melka Avatar asked Apr 08 '16 11:04

Martin Melka


2 Answers

Your repository need to extends JpaRepository, and when you do that. You can use saveAndFlush method from that repository. That mean, you code will be immediately executed on database and exception will be throwed before finish transaction and you will be able to catch it in Catch block. I added also sample for deleting operation.

Repository:

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserDAO extends JpaRepository<User, Long> {
}

Service:

 public class UserService {
        private UserDAO userDAO;

        (...)

        @Transactional
        public User createUser(...){
            (...)
            try{
                userDAO.saveAndFlush(newUserObject);
            } catch(DataIntegrityViolationException e){
                throw new UserAlreadyExistsException(username);
            }
        }

        @Transactional
        public void deleteUser(...){
            (...)
            try{
                userDAO.delete(deletingUserObject);
                userDAO.flush();
            } catch(DataIntegrityViolationException e){
                throw new UserException(username);
            }
        }
    }
like image 80
mflorczak Avatar answered Nov 17 '22 11:11

mflorczak


Annotate your service method with

@Transactional(rollbackFor = UserAlreadyExistsException.class)

it will tell spring to not commit the transaction if the Exception is thrown so you will be able to catch it in your controller

like image 30
black4bird Avatar answered Nov 17 '22 11:11

black4bird