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 を参照してください。