Spring Bootのエンティティーの精査方法のメモ。
Spring Boot(1.5.6)ではJSONをエンティティーに変換して受け取ることが出来るが、そのエンティティーに対して精査(バリデーション)をすることも出来る。
精査にはHibernateを使っているようだが、spring-boot-starter-data-restが依存関係に入っていれば、自動的に使用できる。
〜
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
runtime('org.springframework.boot:spring-boot-devtools')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')
}
RestControllerでJSONを受け取る際に精査を行う例。
package com.example.demo.entity; import javax.validation.constraints.Digits; import javax.validation.constraints.NotNull;
public class ValidateExampleEntity { @NotNull public String value1; @Digits(integer = 3, fraction = 1) public String value2; }
精査を行いたいエンティティーには、条件を表すアノテーションを付ける。
例えば@NotNullアノテーションは必須項目(null以外)、@Digitsアノテーションは数値(整数部の最大桁数と小数部の最大桁数を指定)。
ちなみに、これらのアノテーションはjavaxパッケージになっているが、JBossのOSSらしい。(Hibernateを入れると、一緒に入ってくる)
package com.example.demo.rest; import java.util.Map; import javax.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.example.demo.entity.ValidateExampleEntity;
@RestController public class ValidateController { @RequestMapping(path = "/valid/example1") public ValidateExampleEntity example1(@RequestBody @Valid ValidateExampleEntity entity) { return entity; }
RestControllerでエンティティーを受け取るメソッドに@Validアノテーションを付けると、そのエンティティーに対して精査が行われる。
デフォルトでは、精査エラーが起きるとMethodArgumentNotValidExceptionが発生するので、その例外に対する例外ハンドラーを作成する。
@ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Map<String, ?> handleException(MethodArgumentNotValidException exception) { MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); BindingResult result = exception.getBindingResult(); for (ObjectError error : result.getGlobalErrors()) { map.add("global", error.getDefaultMessage()); } for (FieldError error : result.getFieldErrors()) { map.add(error.getField(), error.getDefaultMessage()); } return map; } }
MethodArgumentNotValidExceptionから精査エラーの情報(BindingResult)を取得できる。
BindingResultは複数の精査エラーを保持していることもある。
(BindingResultはErrorsインターフェースを実装している)
正常系 | 精査エラー | |
---|---|---|
JSON | { |
{ |
↓ | ↓ | |
実行結果 | { |
{ |
精査エラーを、例外ハンドラーを介さずにメソッドで直接受け取ることも出来る。
import org.springframework.validation.Errors; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError;
@RequestMapping(path = "/valid/example2") public Object example2(@RequestBody @Valid ValidateExampleEntity entity, Errors errors) { if (!errors.hasErrors()) { return entity; } MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); for (ObjectError error : errors.getGlobalErrors()) { map.add("global", error.getDefaultMessage()); } for (FieldError error : errors.getFieldErrors()) { map.add(error.getField(), error.getDefaultMessage()); } return map; }
メソッドの引数にErrorsを追加する。
精査エラーがあると、Errorsに精査エラーの情報が入ってくる。
正常系 | 精査エラー | |
---|---|---|
JSON | { |
{ |
↓ | ↓ | |
実行結果 | { |
value2=numeric+value+out+of+bounds+%28%3C3+digits%3E.%3C1+digits%3E+expected%29&value1=may+not+be+null |
なんか、精査エラー時の結果がJSONになってないけど^^;
@RequestMappingアノテーションのproducesで「application/json」を指定してやると、(精査エラー時でも)JSONで返るようだ。
参考: stackoverflowのSpring MVC - How to return simple String as JSON in Rest Controller
import org.springframework.http.MediaType;
@RequestMapping(path = "/valid/example2", produces = MediaType.APPLICATION_JSON_VALUE) public Object example2(@RequestBody @Valid ValidateExampleEntity arg, Errors errors) { 〜 }
正常系 | 精査エラー | |
---|---|---|
JSON | { |
{ |
↓ | ↓ | |
実行結果 | { |
{ |
なお、Errorsを受け取る方法では精査エラー時も当然HTTPステータスOK(200)で返るので、それを変えたいなら、ResponseEntityを返すようにする。
import org.springframework.http.ResponseEntity;
@RequestMapping(path = "/valid/example3", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<?> example3(@RequestBody @Valid ValidateExampleEntity entity, Errors errors) { if (!errors.hasErrors()) { return new ResponseEntity<>(entity, HttpStatus.OK); } MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); for (ObjectError error : errors.getGlobalErrors()) { map.add("global", error.getDefaultMessage()); } for (FieldError error : errors.getFieldErrors()) { map.add(error.getField(), error.getDefaultMessage()); } return new ResponseEntity<>(map, null, HttpStatus.BAD_REQUEST); }