Doma2の楽観ロックで排他制御する

Doma2の@Versionアノテーションを使用した楽観ロック方式で実現します。

Webの場合 は更新前のバージョン番号をセッションに保存しますが、RESTful Webサービスの場合はリクエストボディに含まれているバージョン番号がデータベースと一致しているかどうかで排他制御します。 そのため、RESTful Webサービスを呼び出すクライアントで、変更しようとしているエンティティのバージョンを保持する必要があります。

Doma2の楽観ロックについては、以下の公式ドキュメントを参照してください。

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

実装例

Controllerでの userService.update(User) の実行時にDoma2の楽観排他制御が行われます。楽観排他に失敗した場合は、Doma2がOptimisticLockExceptionを送出します。 OptimisticLockExceptionは、 doma-spring-boot-starter がSpringのOptimisticLockingFailureExceptionに変換して再送出します。 詳細は、以下を参照してください。

送出されたOptimisticLockingFailureExceptionはアプリケーション全体で横断的に処理することで、個別の機能で例外処理する必要がなくなります。詳細は、 例外ハンドリング を参照してください。

Controller
@PostMapping("/{id:[\\d]+}")
public User updateUser(@PathVariable Long id, @RequestBody @Valid UserUpdateForm form) {
    // リクエストボディのバージョン番号を、@Versionのついたプロパティに設定して更新します。
    // リクエストボディのバージョン番号が、データベースのバージョン番号と一致しない場合は楽観ロックエラーとなり、OptimisticLockingFailureExceptionが発生します。
    // 更新に成功した場合は、新しいバージョン番号を持ったエンティティを返します。
    return userService.update(new User(id, form.getName(), form.getRole(), form.getAge(), form.getVersionNo())).getEntity();
}
Dao
/**
 * 楽観ロックエラーが発生した場合は、{@link org.springframework.dao.OptimisticLockingFailureException}が送出されます。
 * 呼出し元で例外を補足してハンドリングしてください。
 * <p/>
 * なお、サンプルアプリケーションでは{@link org.springframework.web.bind.annotation.RestControllerAdvice}を使用して、
 * 全てのControllerを横断したエラーハンドリングを実施しています。
 *
 * @see keel.apierrorhandling.GlobalExceptionHandler
 */
@Update
Result<User> update(User user);
Entity
// 楽観ロック用のバージョンカラムには、@Versionを付与します。
@Version
public final Long versionNo;
GlobalExceptionHandler

サンプルアプリケーションで、アプリケーション全体の例外を横断的にハンドリングしているクラスです。

/**
 * {@link ResponseEntityExceptionHandler}がハンドリングしない例外については、{@link ExceptionHandler}を使用してハンドリングします。
 * 楽観ロック例外が発生した場合は、HTTPステータスコードに409を設定します。
 */
@ExceptionHandler(OptimisticLockingFailureException.class)
@ResponseStatus(value = HttpStatus.CONFLICT)
public void handleOptimisticLockingFailureException() {
}

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