I have this spring service:
@Service
@Transactional
public class ConsorcioServiceImpl implements ConsorcioService {
...
@Autowired
private ConsorcioRepository consorcioRepository;
@Override
public void saveBank(Consorcio consorcio) throws BusinessException {
try {
consorcioRepository.save(consorcio);
}
catch(DataIntegrityViolationException divex) {
if(divex.getMessage().contains("uq_codigo")) {
throw new DuplicatedCodeException(divex);
}
else {
throw new BusinessException(dives);
}
}
catch (Exception e) {
throw new BusinessException(e);
}
}
}
That service uses this Spring Data repository:
@Repository
public interface ConsorcioRepository extendsCrudRepository<Consorcio, Integer> {
}
I'm calling the service from a spring controller:
@Controller
@RequestMapping(value = "/bank")
public class BancaController {
@Autowired
private ConsorcioService consorcioService;
@RequestMapping(value="create", method=RequestMethod.POST)
public ModelAndView crearBanca(@Valid BancaViewModel bancaViewModel, BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
ModelAndView modelAndView;
MessageViewModel result;
try {
consorcioService.saveBank(bancaViewModel.buildBanca());
result = new MessageViewModel(MessageType.SUCESS);
redirectAttributes.addFlashAttribute("messageViewModel", result);
modelAndView = new ModelAndView("redirect:/banca/crear");
return modelAndView;
} catch (Exception e) {
result = new MessageViewModel(MessageType.ERROR);
modelAndView = new ModelAndView("crear-bancas");
modelAndView.addObject("messageViewModel", result);
return modelAndView;
}
}
But the exception I get in the controller is: org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
instead of the DuplicatedCodeException
I throw in the service. I need to identify the type of exception so I can give a custom friendly user message.
also your DuplicatedCodeException , BusinessException should be runtime exception , or add for method saveBank :
@Transactinal(rolbackFor={BusinessException.class,DuplicatedCodeException.,class })
in other case spring will not rollback transaction.
from Spring documentation:
While the EJB default behavior is for the EJB container to
automatically roll back the transaction on a system exception (usually
a runtime exception), EJB CMT does not roll back the transaction
automatically on an application exception (that is, a checked
exception other than java.rmi.RemoteException). While the Spring
default behavior for declarative transaction management follows EJB
convention (roll back is automatic only on unchecked exceptions), it
is often useful to customize this.
Just add catch (TransactionSystemException tse)
before catch (Exception e)
branch and then extract your exception with getOriginalException()
.
try {
consorcioService.saveBank(bancaViewModel.buildBanca());
result = new MessageViewModel(MessageType.SUCESS);
redirectAttributes.addFlashAttribute("messageViewModel", result);
modelAndView = new ModelAndView("redirect:/banca/crear");
return modelAndView;
} catch (TransactionSystemException tse) {
final Throwable ex = tse.getOriginalException();
if (ex instanceof DuplicatedCodeException) {
// DuplicatedCodeException
}
} catch (Exception e) {
result = new MessageViewModel(MessageType.ERROR);
modelAndView = new ModelAndView("crear-bancas");
modelAndView.addObject("messageViewModel", result);
return modelAndView;
}
That happens because your exception is wrapped in RollbackException
as Throwable
cause. In it's turn RollbackException
is also a cause of TransactionSystemException
.
You can build global exception handler to catch and customize all exceptions as you wish:
@ControllerAdvice
class GlobalControllerExceptionHandler {
@ResponseStatus(HttpStatus.CONFLICT)
@ExceptionHandler(TransactionSystemException.class)
public ModelAndView handleDupilatedCode(HttpServletRequest req, TransactionSystemException ex) {
// Build you exception body here
Throwable e = ex.getOriginalException();
if(e instanceof DuplicatedCodeException)
// throw
// Or build custom exception as you want
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
@ControllerAdvice
is available since Spring 3.2
Also you can use @ExceptionHandler
at the @Controller
level, or create this handling in abstract-controller if you have one as a super class of all your controllers.
public class FooController {
//...
@ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
//
}
}
There are some other approaches, for full reference please follow the:
Spring IO: Exception handling in Spring MVC
Baeldung: Exception handling with Spring