I have a REST Spring boot app with Hibernate. For simplicity let's assume this workflow:
@Transactional
, do some business logic and call Persistence methodsThe database has a unique
constraint on username
of a User. The way I have it working now is this:
DataViolationException
occurs, Service returns a custom ExceptionThe 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With