ハンズオンセクション 8

概要

さて、ハンズオンの続きです。

"Behaviorで更新、削除 (+ 排他制御、共通カラム)" を学んでいきましょう。

事前準備

src/main/java 配下に org.docksidestage.handson.logic.HandsOn08Logic クラスを作成してください。この時点では空っぽで構いません。また、ERDを開いておくと良いでしょう。

【事務連絡】org.dbflute.handson から、org.docksidestage.handson に変わりました。 org.dbfluteで開始した人は、そのまま org.dbflute で続けてOKです。もし、移行するなら log4j.properties と basicInfoMap.dfprop の該当箇所を修正してください。

排他制御

実装する前に、更新処理でついてまわる "排他制御" という概念を理解しましょう。

排他制御ありの更新

それでは、まずは排他制御ありの更新です。

以下のロジックを作成してみましょう。

ロジックのメソッド
void updateMemberChangedToFormalized(Integer memberId, Long versionNo)
  • 指定された会員を正式会員に更新する
  • 排他制御ありで更新する
  • 引数の値で null は許されない
対応テストメソッド
test_updateMemberChangedToFormalized_会員が更新されていること()
  • 任意の仮会員の会員IDとバージョンNOを渡して更新すること
  • テストデータ上で任意の仮会員のIDが何番なのかに依存しないように
  • 更新処理後、DB上のデータが更新されていることをアサート
test_updateMemberChangedToFormalized_排他制御例外が発生すること()
  • 何かしらの方法で排他制御例外を発生させてみること
  • 排他制御例外の内容をログに出力して目視確認すること

共通カラムの設定

既に共通カラムの設定は前回のセクションで完了しています。なので、更新日時や更新ユーザは明示的に設定する必要はありません。

排他制御なしの更新

それでは排他制御なしの更新のエクササイズです。

ロジックのメソッド
void updateMemberChangedToFormalizedNonstrict(Integer memberId)
  • 指定された会員を正式会員に更新する
  • 排他制御なしで更新する
  • 引数の値で null は許されない
対応テストメソッド
test_updateMemberChangedToFormalizedNonstrict_会員が更新されていること()
  • 任意の仮会員の会員IDを渡して更新すること
  • 更新処理後、DB上のデータが更新されていることをアサート
test_updateMemberChangedToFormalizedNonstrict_排他制御例外が発生しないこと()
  • 通常なら排他制御例外が起きるはずの状況でも排他制御例外が発生しないことをアサート

排他制御なしの削除

それでは排他制御なしの削除のエクササイズです。

ロジックのメソッド
void deletePurchaseSimply(Integer memberId)
  • 指定された会員の購入を排他制御なしで削除する ※queryDelete(...)
  • 苦難があっても頑張って削除する
  • 引数の値は null も許される (null なら何もしない)
対応テストメソッド
test_deletePurchaseSimply_購入が削除されていること()
  • 任意の正式会員の会員IDを渡して削除すること
  • 削除処理後、DB上のデータが削除されていることをアサート

もしよければ、デッドロックを

せっかくなので、ちょっとデッドロックしてみましょう。(講師によるデッドロック話あり)

※ロジックは作らずにテストクラスに直接実装
---
テストメソッド
test_IfYouLike_DeadLock()
  • cannonball()メソッドを使ってデッドロックを発生させてみること
  • 例外メッセージに "Deadlock" という文字が含まれていることをアサート

ハンズオンで使っている UTFlute のドキュメントをじっくり読んでから実装してみましょう。

e.g. cannonball()を使ってデッドロックの参考コード @Java
cannonball(new CannonballRun() {
    public void drive(CannonballCar car) {
        car.projectA(new CannonballProjectA() {
            public void plan(CannonballDragon dragon) {
                dragon.expectNormallyDone();
                updateFoo();
            }
        }, 1);
        car.projectA(new CannonballProjectA() {
            public void plan(CannonballDragon dragon) {
                dragon.expectNormallyDone();
                updateBar();
            }
        }, 2);
        car.projectA(new CannonballProjectA() {
            public void plan(CannonballDragon dragon) {
                dragon.expectOvertime();
                updateBar();
            }
        }, 1);
        car.projectA(new CannonballProjectA() {
            public void plan(CannonballDragon dragon) {
                updateFoo();
            }
        }, 2);
    }
}, new CannonballOption().threadCount(2).expectExceptionAny("Deadlock"));

さらに、MySQLでは様々なデッドロックが存在します。ブログ記事をご覧下さい。

リファクタリングしましょう!

それぞれのエクササイズで、同じようなプログラムがありませんでしょうか?...例えば、任意の仮会員を検索するような処理とか。

テストクラスとはいえ、あまり冗長するとメンテナンスが大変です。まあ、このケースだととても小さなプログラムですが、 トレーニングだと思って、少なくとも "任意" というロジックが一箇所になるといいですね。うまく private メソッドを作って再利用するようにしましょう。

もしできるならば、IDE (e.g. Eclipseとか) の機能を使って、メソッドの抽出をするとよいでしょう。 (Eclipseなら、メソッドにしたい部分を選択して、ctrl+1 => extract to method)

コードの再利用って難しい!?

同じコードが二つあったら再利用?

結果的にそうなるケースが多いですが、厳密には 意味が同じだから再利用 をすべきです。

業務ロジックというのは非常に複雑なもので、同じコードがあったとしても、たまたま同じコードになっただけで、目的や背景が違うというようなことがあります。

そういうときに安易に再利用してしまうと、ちょっとした要件の変更などで、すぐに同じではなくなってしまいます。 すると、あまりコードは修正したくないという思いが働き、無理矢理メソッドの中で if 文で分岐させてつじつまを合わせたり...オプションのような形で綺麗にできればOKですが、 積み重なるとどんどんカオスになり...A画面の修正なのに、関係ないはずのB画面がバグったり...

多くの場合でコードが同じであれば意味も同じなので、なおさらやっかいです。時々 "だけ" 違うことがあるみたいな。普段から、そのロジックの 意味や役割を考えてコードに接する意識 が大切だと考えます。

次のセクション

さて、次のセクションへ