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)
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
}
}
何かを追加したいだけです。私は同じことをしようとして、エンティティを検証していました。それから、コントローラーの入力を検証すると、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"
}
すべての例外を確認し、必要なものを選択してください
原因を特定する必要があります:
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));
}
次のソリューションは、Spring Boot 2.1.2に基づいています。
物事を明確にするために nimai としてすでに正しく言及されています:
ConstraintViolationException.classはコードのそのレイヤーに伝播されず、下位レイヤーでキャッチされ、ラップされて別のタイプで再スローされるため、キャッチできません。そのため、Webレイヤーにヒットする例外はConstraintViolationExceptionではありません。
あなたの場合、それはおそらくDataIntegrityViolationException
であり、永続層の問題を指摘しています。しかし、あなたはそれをそんなに遠くに来させたくありません。
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){
...
}
これにより、永続化レイヤーに到達する前に、必要なステータスコード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
が生成されます。これは、ResponseEntityExceptionHandler
がMethodArgumentNotValidException
。したがって、何も拡張せずに、それを別の@ControllerAdvice
クラスに入れるだけです。
メールパターンの検証を手動でトリガーできることも確認しました(「 手動でSpring Annotation Validationを呼び出す 」を参照)。私はそれを自分でテストしませんでしたが、私は個人的にそのアプローチが好きではありません。なぜなら、それはコントローラーコードを肥大化させているだけであり、現在それを必要とするユースケースを考えることができないからです。
私はそれが同様の問題に遭遇している他の人を助けることを願っています。
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());
}
それが私の解決策です...
@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() {
}
...
}
これを@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);
}
@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
。
あなたが正しい_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);
}
}
これは期待どおりに呼び出されます。
_@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>();
....
}
_