データベースを使用して入力値をチェックする

データベースを使用した入力値のチェックの概要については、Webでのデータベースを使用した入力値のチェック を参照してください。

ここでは、RESTful Webサービスでデータベースを使用した入力値チェックを行った場合の例外ハンドリングとクライントへのレスポンス返却方法について説明します。

以下のサンプルコードの動作確認環境については、 動作確認環境と依存ライブラリについて を参照してください。

実装例

Controller

ControllerではServiceで送出された例外を捕捉します。 捕捉した例外を元に、クライアントに返すための情報を持つ例外を生成し送出します。

private static final String ROLE_NOT_FOUND_MESSAGE = "keel.api-error-handling.role-not-found";

private final UserService userService;

public UsersController(UserService userService) {
    this.userService = userService;
}

@GetMapping
public List<User> getAllUsers() {
    return userService.findAllUsers();
}

@PostMapping
public ResponseEntity<User> create(@RequestBody @Valid UserForm form, UriComponentsBuilder builder) {
    try {
        User created = userService.insert(new User(form.getName(), form.getRole(), form.getAge())).getEntity();
        return ResponseEntity.created(builder.pathSegment("users", "{id}").buildAndExpand(created.id).toUri()).body(created);
    } catch (UserService.RoleNotFoundException e) {
        // ロールがデータベースのロールテーブル上に存在しないという例外を捕捉し、入力値と紐づけて例外を送出します。
        BindingResult bindingResult = new BeanPropertyBindingResult(form, "");
        bindingResult.rejectValue("role", ROLE_NOT_FOUND_MESSAGE, new Object[]{form.getRole()}, ROLE_NOT_FOUND_MESSAGE);
        throw new CustomValidationException(bindingResult);
    }
}
メッセージ定義(messages.properties)

クライアントに返すメッセージを定義します。

keel.api-error-handling.MethodArgumentNotValidException=入力項目に誤りがあります。
keel.api-error-handling.HttpMessageNotReadableException=入力形式に誤りがあります。
keel.api-error-handling.user-not-found=ユーザーが見つかりませんでした。
keel.api-error-handling.role-not-found=ロール[{0}]が見つかりませんでした。
例外クラス(CustomValidationException)

バリデーションエラーの情報を保持する例外クラスです。 例外クラスがバリデーションエラーの情報を持つことで、例外からクライアントに返す情報を構築できます。

package keel.apierrorhandling.exception;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;

/**
 * データベースを使用した入力値チェックがエラーになった場合など、 {@link Controller} から送出する例外クラスです。
 * <p>
 * Formの入力値などと関連付けるために {@link BindingResult} を保持しています。
 */
public class CustomValidationException extends RuntimeException {

    private final BindingResult bindingResult;

    public CustomValidationException(BindingResult bindingResult) {
        this.bindingResult = bindingResult;
    }

    public BindingResult getBindingResult() {
        return this.bindingResult;
    }

    @Override
    public String getMessage() {
        return this.bindingResult.toString();
    }
}
例外ハンドラクラス(GlobalExceptionHandler)

例外ハンドリング を使用して、例外に応じた処理を行います。 この例では、捕捉した例外を元にクライアントに返却するレスポンスを構築します。

/**
 * {@link ResponseEntityExceptionHandler} がハンドリングしない例外については、 {@link ExceptionHandler} を使用してハンドリングします。
 * 独自に作成した {@link CustomValidationException} が発生した場合は、HTTPステータスコードに400を設定し、エラー内容を返却しています。
 */
@ExceptionHandler(CustomValidationException.class)
public ResponseEntity<Object> handleCustomValidationException(CustomValidationException ex, WebRequest request) {
    return super.handleExceptionInternal(
            ex,
            body(ex.getBindingResult()),
            new HttpHeaders(),
            HttpStatus.BAD_REQUEST,
            request);
}

private List<ApiErrorResponse> body(BindingResult bindingResult) {
    return bindingResult
            .getFieldErrors()
            .stream()
            .map(fieldError -> new ApiErrorResponse(
                    fieldError.getField(),
                    messageSource.getMessage(fieldError, LocaleContextHolder.getLocale())))
            .collect(Collectors.toList());
}

private ApiErrorResponse body(String code) {
    String message = messageSource.getMessage(code, null, LocaleContextHolder.getLocale());
    return new ApiErrorResponse(null, message);
}
レスポンス内容

上記の実装例の場合、バリデーションエラー発生時のレスポンスボディの内容は以下のようになります。

[
  {
    "field": "role",
    "message": "ロール[存在しないロール]が見つかりませんでした。"
  }
]

サンプル全体は api-error-handling-sample を参照してください。