I need to show custom messages in my Spring 3.0 application. I have a database with Hibernate and there are several constraints. I have doubts in how DataIntegrityViolationException
should be handled in a good way. I wonder if there is a way to map the exception with a message set in a properties file, as it is possible in Constraints validation. Could I handle it automatically in any way or I have to catch this exception in each controller?
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
:
@RestControllerAdvice
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)
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);
}
// https://mcmap.net/q/225569/-java-find-the-first-cause-of-an-exception
@NonNull
private static Throwable getRootCause(@NonNull Throwable t) {
Throwable rootCause = NestedExceptionUtils.getRootCause(t);
return rootCause != null ? rootCause : t;
}
...
}
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);
}
UK_6DOTKOTT2KJSP8VW4D0M25FB7_INDEX_4
–
Jag uniqueConstraints
parameter in @Table
annotation, e.g. @Table(name = "meals", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "date_time"}, name = "meals_unique_user_datetime_idx")})
–
Deathly 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.
1. In your request body class check for not null or not empty like this
public class CustomerRegisterRequestDto {
@NotEmpty(message = "first name is empty")
@NotNull(message = Constants.EntityValidators.FIRST_NAME_NULL)
private String first_name;
//other fields
//getters and setters
}
2. Then in your service check for this
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<CustomerRegisterRequestDto>> violations = validator.validate(userDto);
if (!violations.isEmpty()) {
//something is wrong in request parameters
List<String> details = new ArrayList<>();
for (ConstraintViolation<CustomerRegisterRequestDto> violation : violations) {
details.add(violation.getMessage());
}
ErrorResponse error = new ErrorResponse(Constants.ErrorResponse.REQUEST_PARAM_ERROR, details);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
3. Here is your ErrorResponse class
© 2022 - 2024 — McMap. All rights reserved.