web-dev-qa-db-ja.com

Spring Bootはorg.hibernate.exception.ConstraintViolationExceptionを処理しません

Entityクラスでメールを検証するためのパターンを定義しました。検証例外ハンドラークラスで、ConstraintViolationExceptionのハンドラーを追加しました。私のアプリケーションはSpringBoot 1.4.5を利用しています。

Profile.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

@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>();
    ....
    }
} 

コードを実行して無効なメールアドレスを渡すと、次の例外が発生します。 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)    
16
bostonjava

ConstraintViolationException.classをキャッチすることはできません。コードのそのレイヤーに伝播されず、下位のレイヤーによってキャッチされ、ラップされて別のタイプで再スローされます。したがって、Webレイヤーにヒットする例外はConstraintViolationExceptionではありません。

私の場合、それはTransactionSystemExceptionです。 Springの@TransactionalアノテーションをJpaTransactionManagerとともに使用しています。 EntityManagerは、トランザクションで問題が発生するとロールバック例外をスローし、TransactionSystemExceptionによってJpaTransactionManagerに変換されます。

だからあなたはこのようなことをすることができます:

@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
    }
}
20
nimai

何かを追加したいだけです。私は同じことをしようとして、エンティティを検証していました。それから、コントローラーの入力を検証すると、Springにはすべての機能がすでに用意されていることに気づきました。

@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid ProfileDto profile){
...    
}

@Validアノテーションは、javax.validationアノテーションで検証をトリガーします。

空白を許可しない正規表現を使用して、プロファイルのユーザー名にパターンアノテーションがあるとします。

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"
}
2
Ena

すべての例外を確認し、必要なものを選択してください

  1. 原因を特定する必要があります:

    while ((cause = resultCause.getCause()) != null && resultCause != cause) {
        resultCause = cause;
    }
    
  2. 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));
    }
    
1
gghnisan

次のソリューションは、Spring Boot 2.1.2に基づいています。

物事を明確にするために nimai としてすでに正しく言及されています:

ConstraintViolationException.classはコードのそのレイヤーに伝播されず、下位レイヤーでキャッチされ、ラップされて別のタイプで再スローされるため、キャッチできません。そのため、Webレイヤーにヒットする例外はConstraintViolationExceptionではありません。

あなたの場合、それはおそらくDataIntegrityViolationExceptionであり、永続層の問題を指摘しています。しかし、あなたはそれをそんなに遠くに来させたくありません。


解決

Ena のように、メソッドパラメータとして指定されたエンティティの@Validアノテーションを使用します。私のバージョンでは、org.springframework.web.bind.annotation.RequestBodyアノテーションがありませんでした(@RequestBodyアノテーションがないと、ProfileDtoProfileDtoエンティティに正しく解析できず、プロパティはnull値になります(例:NullPointerException)。

@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid @RequestBody ProfileDto profile){
    ...
}

これにより、永続化レイヤーに到達する前に、必要なステータスコード400とorg.springframework.web.bind.MethodArgumentNotValidExceptionを伴うデフォルトの応答本文が返されます。 MethodArgumentNotValidExceptionの処理は、org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandlerで定義されています。

これは別のトピックですが、_@ExceptionHandler(MethodArgumentNotValidException.class)@ControllerAdviceを作成してその動作をオーバーライドし、必要に応じてレスポンスボディをカスタマイズするオプションがあります。デフォルトのエラーレスポンスボディは最適ではなく、ErrorMvcAutoConfigurationを除外すると存在しないためです。

注意:ResponseEntityExceptionHandlerを拡張する@ControllerAdvice内の@ExceptionHandler(MethodArgumentNotValidException.class)を検索すると、IllegalStateExceptionが生成されます。これは、ResponseEntityExceptionHandlerMethodArgumentNotValidException。したがって、何も拡張せずに、それを別の@ControllerAdviceクラスに入れるだけです。


別の手動アプローチ

メールパターンの検証を手動でトリガーできることも確認しました(「 手動でSpring Annotation Validationを呼び出す 」を参照)。私はそれを自分でテストしませんでしたが、私は個人的にそのアプローチが好きではありません。なぜなら、それはコントローラーコードを肥大化させているだけであり、現在それを必要とするユースケースを考えることができないからです。

私はそれが同様の問題に遭遇している他の人を助けることを願っています。

1
kluckow

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());
}
1
Saeid Babaei

それが私の解決策です...

@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());
    }

public class ApiErrorsListResponse {

    private HttpStatus status;

 private List<String> errorMessage;

    public ApiErrorsListResponse() {
    }
...
}

0
skyho

これを@controllerAdviceに追加することで、org.hibernate.exception.ConstraintViolationExceptionを処理できます。

@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);

}
0
anju kumari
@ResponseBody
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
@ExceptionHandler(DataIntegrityViolationException.class)
public Map errorHandler(DataIntegrityViolationException ex) {
    Map map = new HashMap();
    map.put("rs_code", 422);
    map.put("rs_msg", "data existed !");
    return map;
}

ただキャッチorg.springframework.dao.DataIntegrityViolationException

0
abai parhat

あなたが正しい_ConstraintViolationExceptionをインポートしたことを再確認します

必要なのは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);
    }
}

これは期待どおりに呼び出されます。

0
Chris Turner

_@ExceptionHandler_に@ResponseStatus(HttpStatus.BAD_REQUEST)を追加する必要があると思います。

_@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
    List<String> errors = new ArrayList<String>();
    ....
}
_
0
Patrick