概要
既に登録済みの書籍情報を画面上で編集し、更新する処理を実装します。
更新した内容が反映されていることを確認するところまでを行います。
フロントエンド
更新情報のリクエスト
edit.html内のformタグを再掲
1 2 3 |
<form th:action="@{/book/create}" th:object="${bookForm}" method="post"> |
/book/create へPostでリクエストすることになっているので、
Controller側でformを受信できるように実装を進めていきます。
バックエンド
リクエストの受信(Controller)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// BookController.java @PostMapping("book/update") public String postUpdate(@Validated BookForm form, BindingResult result) { // ISBNコードの存在チェック if(service.existsIsbn(form.getIsbn(), form.getId())) { result.rejectValue("isbn", "existsIsbn"); } // エラーチェック(Form) if(result.hasErrors()) { return "book/edit"; } // 更新処理(楽観ロックあり) try { service.update(new Book(form)); } catch (OptimisticLockingFailureException e) { result.rejectValue("id", "updated"); return "book/edit"; } return "book/list"; } |
・メソッド名を「postUpdate」とします。
これは送信されたリクエストメソッド(post)と処理内容(update)を組み合わせています。
バリデーションチェックは登録時と同じように@Validatedをつけておきます。
・ISBNコードの存在チェック
既に登録済みのISBNコードと重複しないように存在チェックを行います。
登録時と異なり、更新前の自身のデータはチェック対象外とするため、
除外用に自身のidを追加の引数として渡します。
・楽観ロック
もし既に更新済みであれば、楽観ロックにより例外が送出される想定のため、
ここで例外をキャッチしてエラーメッセージを表示させるようにします。
(悲観ロックという方法もありますが、ロック対象が1件で処理時間もごく短いため、
楽観ロックでも十分です)
・画面遷移
エラーがあれば編集画面に戻します。
エラーが無ければ検索画面に遷移させます。
処理の委譲(Service)
存在チェック
1 2 3 4 5 6 7 |
// BookService.java public boolean existsIsbn(String isbn, int id) { return repository.existsIsbn(isbn, id) == null ? false : true; } |
自身(id)を除く結果が無ければfalse[存在しない]、あればtrue(存在する)を返します。
BookRepositoryのexistsIsbnは、引数を2つ持つメソッドが無いため、
オーバーロードしたメソッドを追加で実装することになります。
更新処理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// BookService.java public void update(Book after) throws OptimisticLockingFailureException { Book before = repository.selectOne(after.getId()); if(before != null && before.getVersion() == after.getVersion()) { String userId = "kondo"; after.setUpdated_user(userId); after.setUpdated_at(new Date()); repository.update(after); return; } throw new OptimisticLockingFailureException("更新データが無いか、すでに更新されています。"); } |
・変数名で更新前後のオブジェクトをわかり易く分ける
after :更新後のオブジェクト
bedore:更新前のオブジェクト
・更新可否のチェック
(条件)更新前(before)が存在していること AND 更新前後Versionが同じであること
更新前(before)が存在していないということは、何らかの処理で削除されているのでfalse
更新前後でVersionが異なるということは、既に更新済なのでfalse
つまり、削除もされておらず、最新の更新処理である場合に更新が行えるようになっています。
・楽観ロック例外の送出(スロー)
上記のチェックでfalseになった場合、楽観ロックの例外(OptimisticLockingFailureException)をスローします。
例外がスローされた場合、更新処理はロールバックされるようになっています。
DB接続(Repository)
existsIsbnメソッドのオーバライド
1 2 3 4 5 |
// BookRepository.java public Book existsIsbn(String isbn, int id); |
idを引数に加えたメソッドを追加しておきます。
updateメソッド
1 2 3 4 5 |
// BookRepository.java public void update(Book book); |
新規でメソッドを追加します。
引数にはBookオブジェクトを渡します。
SQL(XMLファイル定義)
existsIsbnにtestを追加
1 2 3 4 5 6 7 8 |
<select id="existsIsbn" resultMap="book"> SELECT * FROM book WHERE isbn = #{isbn} AND deleted_at IS NULL <if test="id != ''"> AND id != #{id} </if> </select> |
idフィールドがある場合で値が入っていれば、検索条件を追加するようにしておきます。
これで更新時には、自身のidを除いた結果だけをSELECTできるようになりました。
updateの追加
1 2 3 4 5 6 7 8 9 |
<update id="update"> UPDATE book SET name = #{name}, isbn = #{isbn}, description = #{description}, publisher = #{publisher}, price = #{price}, publication_date = #{publication_date}, updated_user = #{updated_user}, updated_at = #{updated_at}, version = version + 1 WHERE id = #{id} AND version = #{version} AND deleted_at IS NULL </update> |
各カラムをUPDATEします。
処理対象は、自身(id)であり、versionが変わっておらず、有効(削除されていない)ものとします。
また、更新時にはVerisonを+1します。
これにより、バージョン管理を行っています。
更新処理の実装を確認
ISBNコードの存在チェック
変更せずに更新可能なこと。
変更したISBNコードが他の書籍情報で有効でない場合、更新可能な事。
変更したISBNコードが他の書籍情報で有効な場合、エラーとなること。
更新内容の反映
(画面上で確認可能)
各画面項目の変更が反映されること
(DB上で確認する)
更新日時が更新されること
更新者が更新されること(固定値のため、ソースコードを変更して確認してよい)
Versionが+1されること
まとめ
・更新時には楽観ロックなど、他のユーザの変更内容が誤って上書きされない仕組みが必要である。
・更新時にVersionは繰り上げてバージョン管理を行うこと。