java SpringBoot 不处理 org.hibernate.exception.ConstraintViolationException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/45070642/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
SpringBoot doesn't handle org.hibernate.exception.ConstraintViolationException
提问by bostonjava
I have defined a pattern for validating email in my Entity class. In my validation exception handler class, I have added handler for ConstraintViolationException. My application utilize SpringBoot 1.4.5.
我在我的实体类中定义了一个验证电子邮件的模式。在我的验证异常处理程序类中,我为 ConstraintViolationException 添加了处理程序。我的应用程序使用 SpringBoot 1.4.5。
Profile.java
配置文件.java
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "profile")
public class Profile extends AuditableEntity {
private static final long serialVersionUID = 8744243251433626827L;
@Column(name = "email", nullable = true, length = 250)
@NotNull
@Pattern(regexp = "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$")
@Size(max = 250)
private String email;
....
}
ValidationExceptionHandler.java
验证异常处理程序.java
@ControllerAdvice
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {
private MessageSource messageSource;
@Autowired
public ValidationExceptionHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex,
WebRequest request) {
List<String> errors = new ArrayList<String>();
....
}
}
When I run my code and pass invalid email address, I get the following exception. The code in handleConstraintViolation is never executed. The http status returned in the exception is 500, but I want to return 400. Any idea how I can achieve that?
当我运行我的代码并传递无效的电子邮件地址时,我收到以下异常。handleConstraintViolation 中的代码永远不会执行。异常中返回的 http 状态是 500,但我想返回 400。知道如何实现吗?
2017-07-12 22:15:07.078 ERROR 55627 --- [nio-9000-exec-2] o.h.c.s.u.c.UserProfileController : Validation failed for classes [org.xxxx.common.service.user.domain.Profile] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$"', propertyPath=email, rootBeanClass=class org.xxxx.common.service.user.domain.Profile, messageTemplate='{javax.validation.constraints.Pattern.message}'}]
javax.validation.ConstraintViolationException: Validation failed for classes [org.xxxx.common.service.user.domain.Profile] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$"', propertyPath=email, rootBeanClass=class org.xxxx.common.service.user.domain.Profile, messageTemplate='{javax.validation.constraints.Pattern.message}'}]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:138)
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:78)
回答by nimai
You cannot catch ConstraintViolationException.class
because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException
.
您无法捕获,ConstraintViolationException.class
因为它没有传播到代码的那一层,而是被较低层捕获,在另一种类型下包装并重新抛出。因此,命中您的 web 层的异常不是ConstraintViolationException
.
In my case, it's a TransactionSystemException
.
I'm using @Transactional
annotations from Spring with the JpaTransactionManager
. The EntityManager throws a rollback exception when somethings goes wrong in the transaction, which is converted to a TransactionSystemException
by the JpaTransactionManager
.
就我而言,它是一个TransactionSystemException
. 我将@Transactional
Spring 中的注释与JpaTransactionManager
. 当事务中出现问题时,EntityManager 会抛出回滚异常,该异常TransactionSystemException
由JpaTransactionManager
.
So you could do something like this:
所以你可以做这样的事情:
@ExceptionHandler({ TransactionSystemException.class })
public ResponseEntity<RestResponseErrorMessage> handleConstraintViolation(Exception ex, WebRequest request) {
Throwable cause = ((TransactionSystemException) ex).getRootCause();
if (cause instanceof ConstraintViolationException) {
Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) cause).getConstraintViolations();
// do something here
}
}
回答by Ena
Just want to add something. I was trying to do the same thing, validating the entity. Then I realized Spring has already everything out of the box if you validate the controller's input.
只是想补充一点。我试图做同样的事情,验证实体。然后我意识到如果您验证控制器的输入,Spring 已经拥有开箱即用的所有功能。
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid ProfileDto profile){
...
}
The @Valid
annotation will trigger the validation with the javax.validation annotations.
该@Valid
批注将使用 javax.validation 批注触发验证。
Suppose you have a Pattern annotation on your profile username with a regexp not allowing whitespaces.
假设您的个人资料用户名上有一个 Pattern 注释,正则表达式不允许空格。
Spring will build a response with status 400 (bad request) and a body like this one:
Spring 将构建一个状态为 400(错误请求)的响应和一个像这样的主体:
{
"timestamp": 1544453370570,
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Pattern.ProfileDto.username",
"Pattern.username",
"Pattern.java.lang.String",
"Pattern"
],
"arguments": [
{
"codes": [
"profileDto.username",
"username"
],
"arguments": null,
"defaultMessage": "username",
"code": "username"
},
[],
{
"defaultMessage": "^[A-Za-z0-9_\-.]+$",
"arguments": null,
"codes": [
"^[A-Za-z0-9_\-.]+$"
]
}
],
"defaultMessage": "must match \"^[A-Za-z0-9_\-.]+$\"",
"objectName": "profileDto",
"field": "username",
"rejectedValue": "Wr Ong",
"bindingFailure": false,
"code": "Pattern"
}
],
"message": "Validation failed for object='profileDto'. Error count: 1",
"path": "/profile"
}
回答by gghnisan
Just check all Exceptions and select the one you need
只需检查所有例外并选择您需要的例外
Need to determine the cause:
while ((cause = resultCause.getCause()) != null && resultCause != cause) { resultCause = cause; }
Use instanceof
@ExceptionHandler(Exception.class) protected ResponseEntity<MyException> handleExceptions(Exception e) { String message; Throwable cause, resultCause = e; while ((cause = resultCause.getCause()) != null && resultCause != cause) { resultCause = cause; } if (resultCause instanceof ConstraintViolationException) { message = (((ConstraintViolationException) resultCause).getConstraintViolations()).iterator().next().getMessage(); } else { resultCause.printStackTrace(); message = "Unknown error"; } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new MyException(message)); }
需要确定原因:
while ((cause = resultCause.getCause()) != null && resultCause != cause) { resultCause = cause; }
使用 instanceof
@ExceptionHandler(Exception.class) protected ResponseEntity<MyException> handleExceptions(Exception e) { String message; Throwable cause, resultCause = e; while ((cause = resultCause.getCause()) != null && resultCause != cause) { resultCause = cause; } if (resultCause instanceof ConstraintViolationException) { message = (((ConstraintViolationException) resultCause).getConstraintViolations()).iterator().next().getMessage(); } else { resultCause.printStackTrace(); message = "Unknown error"; } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new MyException(message)); }
回答by Saeid Babaei
You cannot catch ConstraintViolationException.class because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException. So you could do something like this:
您无法捕获 ConstraintViolationException.class,因为它没有传播到代码的那一层,而是被较低层捕获、包装并重新抛出到另一种类型下。因此,命中您的 web 层的异常不是 ConstraintViolationException。所以你可以做这样的事情:
@ExceptionHandler({TransactionSystemException.class})
protected ResponseEntity<Object> handlePersistenceException(final Exception ex, final WebRequest request) {
logger.info(ex.getClass().getName());
//
Throwable cause = ((TransactionSystemException) ex).getRootCause();
if (cause instanceof ConstraintViolationException) {
ConstraintViolationException consEx= (ConstraintViolationException) cause;
final List<String> errors = new ArrayList<String>();
for (final ConstraintViolation<?> violation : consEx.getConstraintViolations()) {
errors.add(violation.getPropertyPath() + ": " + violation.getMessage());
}
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, consEx.getLocalizedMessage(), errors);
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
final ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred");
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
回答by kluckow
Following solution is based on Spring Boot 2.1.2.
以下解决方案基于 Spring Boot 2.1.2。
To clarify things... as nimaialready correctly mentioned:
为了澄清事情......正如nimai已经正确提到的:
You cannot catch ConstraintViolationException.class because it's not propagated to that layer of your code, it's caught by the lower layers, wrapped and rethrown under another type. So that the exception that hits your web layer is not a ConstraintViolationException.
您无法捕获 ConstraintViolationException.class,因为它没有传播到代码的那一层,而是被较低层捕获、包装并重新抛出到另一种类型下。因此,命中您的 web 层的异常不是 ConstraintViolationException。
In your case it is probably a DataIntegrityViolationException
, which points out a problem in the persistence layer. But you don't want to let it come that far.
在您的情况下,它可能是 a DataIntegrityViolationException
,它指出了持久层中的问题。但你不想让它走那么远。
Solution
解决方案
Make use of the @Valid
annotation for the entity given as method parameter as Enamentioned. On my version it was missing the org.springframework.web.bind.annotation.RequestBody
annotation (Without the @RequestBody
annotation the ProfileDto
cannot be parsed correctly into your ProfileDto
entity and the properties are resulting in null
values, e.g. NullPointerException
.):
使用Ena提到的@Valid
作为方法参数给出的实体的注释。在我的版本中,它缺少注释(如果没有注释,则无法将其正确解析到您的实体中,并且属性会产生值,例如。):org.springframework.web.bind.annotation.RequestBody
@RequestBody
ProfileDto
ProfileDto
null
NullPointerException
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid @RequestBody ProfileDto profile){
...
}
This will then return your wanted status code 400 and some default response body accompanied by a org.springframework.web.bind.MethodArgumentNotValidException
before even reaching the persistence layer. The processing of the MethodArgumentNotValidException
is defined in org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
.
这将org.springframework.web.bind.MethodArgumentNotValidException
在到达持久层之前返回您想要的状态代码 400 和一些默认响应正文,并伴有。的处理在MethodArgumentNotValidException
中定义org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
。
This is another topic, but you then have the option to override that behaviour by creating a @ControllerAdvice
with @ExceptionHandler(MethodArgumentNotValidException.class)
and customize the response body to your needs, since the default error response body is not optimal and not even present when excluding ErrorMvcAutoConfiguration.
这是另一个主题,但是您可以选择通过创建一个@ControllerAdvice
with@ExceptionHandler(MethodArgumentNotValidException.class)
并根据您的需要自定义响应正文来覆盖该行为,因为默认错误响应正文不是最佳的,甚至在排除 ErrorMvcAutoConfiguration 时也不存在。
Caution:Locating the @ExceptionHandler(MethodArgumentNotValidException.class)
inside the @ControllerAdvice
that extends the ResponseEntityExceptionHandler
results into an IllegalStateException
, because in the ResponseEntityExceptionHandler
already is an exception handler defined for MethodArgumentNotValidException
. So just put it into another @ControllerAdvice
class without extending anything.
注意:将结果扩展到的@ExceptionHandler(MethodArgumentNotValidException.class)
内部定位到,因为在 中已经是为 定义的异常处理程序。因此,只需将其放入另一个类中,无需扩展任何内容。@ControllerAdvice
ResponseEntityExceptionHandler
IllegalStateException
ResponseEntityExceptionHandler
MethodArgumentNotValidException
@ControllerAdvice
Alternative manual approach
替代手动方法
I saw you can also trigger the validation of the email pattern manually (see Manually call Spring Annotation Validation). I didn't test it myself, but I personally don't like that approach, because it is just bloating your controller code and I currently can't think of a use case that requires it.
我看到您还可以手动触发电子邮件模式的验证(请参阅手动调用 Spring 注释验证)。我自己没有测试过,但我个人不喜欢这种方法,因为它只会使您的控制器代码膨胀,而且我目前想不出需要它的用例。
I hope that helps others encountering a similar issue.
我希望能帮助其他遇到类似问题的人。
回答by skyho
That is my solution...
这就是我的解决方案...
@ExceptionHandler({DataIntegrityViolationException.class})
protected ResponseEntity<Object> handlePersistenceException(final DataIntegrityViolationException ex) {
Throwable cause = ex.getRootCause();
if (cause instanceof SQLIntegrityConstraintViolationException) {
SQLIntegrityConstraintViolationException consEx = (SQLIntegrityConstraintViolationException) cause;
final ApiErrorResponse apiError = ApiErrorResponse.newBuilder()
.message(consEx.getLocalizedMessage())
.status(HttpStatus.BAD_REQUEST)
.build();
return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus());
}
final ApiErrorResponse apiError = ApiErrorResponse.newBuilder()
.message(ex.getLocalizedMessage())
.status(HttpStatus.NOT_ACCEPTABLE)
.build();
return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus());
}
@ExceptionHandler(RollbackException.class)
public ResponseEntity<ApiErrorsListResponse> handleNotValidException(RollbackException ex){
String errMessage = ex.getCause().getMessage();
List<String> listErrMessage = getListErrMessage(errMessage);
ApiErrorsListResponse response = ApiErrorsListResponse.newBuilder()
.status(HttpStatus.NOT_ACCEPTABLE)
.errorMessage(listErrMessage)
.build();
return new ResponseEntity<>(response, HttpStatus.NOT_ACCEPTABLE);
}
public static List<String> getListErrMessage(String msg){
Stream<String> stream = Arrays.stream(msg.split("\n"))
.filter(s -> s.contains("\t"))
.map(s -> s.replaceAll("^([^\{]+)\{", ""))
.map(s -> s.replaceAll("[\"]", ""))
.map(s -> s.replaceAll("=", ":"))
.map(s -> s.replaceAll("interpolatedMessage", "message"))
.map(s -> s.replaceAll("\{|\}(, *)?", ""));
return stream.collect(Collectors.toList());
}
- bean
- 豆角,扁豆
public class ApiErrorsListResponse {
private HttpStatus status;
private List<String> errorMessage;
public ApiErrorsListResponse() {
}
...
}
回答by arun
Try this way..
试试这个方法。。
@ControllerAdvice
public class ControllerAdvisor extends ResponseEntityExceptionHandler {
@Autowired
BaseResponse baseResponse;
@ExceptionHandler(javax.validation.ConstraintViolationException.class)
public ResponseEntity<BaseResponse> inputValidationException(Exception e) {
baseResponse.setMessage("Invalid Input : " + e.getMessage());
return new ResponseEntity<BaseResponse>(baseResponse, HttpStatus.BAD_REQUEST);
}
}
回答by anju kumari
You can handle org.hibernate.exception.ConstraintViolationException by adding this in your @controllerAdvice
您可以通过在 @controllerAdvice 中添加它来处理 org.hibernate.exception.ConstraintViolationException
@ExceptionHandler(DataIntegrityViolationException.class) public ResponseEntity handleConstraintViolationException(Exception ex){
@ExceptionHandler(DataIntegrityViolationException.class) public ResponseEntity handleConstraintViolationException(Exception ex){
String errorMessage = ex.getMessage();
errorMessage = (null == errorMessage) ? "Internal Server Error" : errorMessage;
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
return new ResponseEntity<ErrorResponseDTO>(
new ErrorResponseDTO( errorMessage ,details), HttpStatus.INTERNAL_SERVER_ERROR);
}
回答by Patrick
I think you should add @ResponseStatus(HttpStatus.BAD_REQUEST)
to your @ExceptionHandler
:
我认为你应该添加@ResponseStatus(HttpStatus.BAD_REQUEST)
到你的@ExceptionHandler
:
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
List<String> errors = new ArrayList<String>();
....
}
回答by Chris Turner
I would double check you've imported the right ConstraintViolationException
我会仔细检查你是否导入了正确的 ConstraintViolationException
The one you want is from the org.hibernate.exception.ConstraintViolationException
package. If you've imported the javax.validation.ConstraintViolationException
it will be skipped as you've experienced.
你想要的是从org.hibernate.exception.ConstraintViolationException
包装。如果您已导入,javax.validation.ConstraintViolationException
它将按照您的经验被跳过。
import org.hibernate.exception.ConstraintViolationException;
@RestController
public class FeatureToggleController {
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
This will be called as expected.
这将按预期调用。