简体   繁体   中英

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM