How to handle DataIntegrityViolationException in Spring?
The problem with showing user-friendly messages in the case of constraint violation is that the constraint name is lost when Hibernate's ConstraintViolationException
is being translated into Spring's DataIntegrityViolationException
.
However, you can customize this translation logic. If you use LocalSessionFactoryBean
to access Hibernate, you can supply it with a custom SQLExceptionTranslator
(see LocalSessionFactoryBean.jdbcExceptionTranslator
). This exception translator can translate a ConstraintViolationException
into your own exception class, preserving the constraint name.
I treat DataIntegrityViolationException
in ExceptionInfoHandler
, finding DB constraints occurrences in root cause message and convert it into i18n message via constraintCodeMap
:
@ControllerAdvice(annotations = RestController.class)@Order(Ordered.HIGHEST_PRECEDENCE + 5)public class ExceptionInfoHandler { @Autowired private final MessageSourceAccessor messageSourceAccessor; private static Map<String, String> CONSTRAINS_I18N_MAP = Map.of( "users_unique_email_idx", EXCEPTION_DUPLICATE_EMAIL, "meals_unique_user_datetime_idx", EXCEPTION_DUPLICATE_DATETIME); @ResponseStatus(value = HttpStatus.CONFLICT) // 409 @ExceptionHandler(DataIntegrityViolationException.class) @ResponseBody public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) { String rootMsg = ValidationUtil.getRootCause(e).getMessage(); if (rootMsg != null) { String lowerCaseMsg = rootMsg.toLowerCase(); for (Map.Entry<String, String> entry : CONSTRAINS_I18N_MAP.entrySet()) { if (lowerCaseMsg.contains(entry.getKey())) { return logAndGetErrorInfo(req, e, VALIDATION_ERROR, messageSourceAccessor.getMessage(entry.getValue())); } } } return logAndGetErrorInfo(req, e, DATA_ERROR); } ...}
Can be simulated in my Java Enterprise training application by adding/editing user with duplicate mail or meal with duplicate dateTime.
UPDATE:
Other solution: use Controller Based Exception Handling:
@RestController@RequestMapping("/ajax/admin/users")public class AdminAjaxController { @ExceptionHandler(DataIntegrityViolationException.class) public ResponseEntity<ErrorInfo> duplicateEmailException(HttpServletRequest req, DataIntegrityViolationException e) { return exceptionInfoHandler.getErrorInfoResponseEntity(req, e, EXCEPTION_DUPLICATE_EMAIL, HttpStatus.CONFLICT); }
Spring 3 provides two ways of handling this - HandlerExceptionResolver
in your beans.xml, or @ExceptionHandler
in your controller. They both do the same thing - they turn the exception into a view to render.
Both are documented here.