ハンズオンセクション 5

概要

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

"業務的one-to-oneとデータチェック" を学んでいきましょう。

事前準備

org.docksidestage.handson.exercise.HandsOn05Test クラスを作成してください。 このクラスは UnitContainerTestCase を継承します。また、ERDを開いておくと良いでしょう。

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

業務的one-to-oneとは?

まずは、以下のテストを作成してください。

会員住所情報を検索
  • 会員名称、有効開始日、有効終了日、住所、地域名称をログに出して確認する
  • 会員IDの昇順、有効開始日の降順で並べる

会員住所情報は、会員から見て "業務的one-to-one" と言えます。

業務的one-to-oneの関連付け

additionalForeignKeyMap.dfprop に、以下の定義を設定してください。

e.g. 会員と会員住所情報に業務的one-to-oneを定義 @additionalForeignKeyMap.dfprop
 ...
    ; FK_MEMBER_MEMBER_ADDRESS_VALID = map:{
        ; localTableName  = MEMBER    ; foreignTableName  = MEMBER_ADDRESS
        ; localColumnName = MEMBER_ID ; foreignColumnName = MEMBER_ID
        ; fixedCondition = 
         $$foreignAlias$$.VALID_BEGIN_DATE <= /*targetDate(Date)*/null
     and $$foreignAlias$$.VALID_END_DATE >= /*targetDate(Date)*/null
        ; fixedSuffix = AsValid
        ; comment = 有効な会員住所 (現在日時を入れれば現在住所)
    }
 ...

そして、再自動生成(DocとGenerate)をし、SchemaHTMLにて関連付いていることを確認してください。 AdditionalForeignKeyの場合は、普通のFKとはちょっとだけ表示が異なっています。

業務的one-to-oneを利用した実装

それでは、業務的one-to-oneを利用して、以下のテストを作成してください。

会員と共に現在の住所を取得して検索
  • SetupSelectのJavaDocにcommentがあることを確認すること
  • 会員名称と住所をログに出して確認すること
  • 現在日付はスーパークラスのメソッドを利用 ("c" 始まりのメソッド)
  • 会員住所情報が取得できていることをアサート
千葉に住んでいる会員の支払済み購入を検索
  • 会員ステータス名称と住所をログに出して確認すること
  • 購入に紐づいている会員の住所の地域が千葉であることをアサート

導出的one-to-oneを利用した実装

それでは、以下の導出的one-to-oneの関連を設定して、エクササイズをやってみましょう。

e.g. 会員と会員ログイン情報に導出的one-to-oneを定義 @additionalForeignKeyMap.dfprop
 ...
    ; FK_MEMBER_MEMBER_LOGIN_LATEST  = map:{
        ; localTableName  = MEMBER    ; foreignTableName  = MEMBER_LOGIN
        ; localColumnName = MEMBER_ID ; foreignColumnName = MEMBER_ID
        ; fixedCondition  = 
$$foreignAlias$$.LOGIN_DATETIME = ($$sqbegin$$
select max(login.LOGIN_DATETIME)
  from MEMBER_LOGIN login
 where login.MEMBER_ID = $$foreignAlias$$.MEMBER_ID
)$$sqend$$
        ; fixedSuffix = AsLatest
    }
 ...

commentは、いい感じになんか入れてみましょう。

最終ログイン時の会員ステータスを取得して会員を検索
  • SetupSelectのJavaDocに自分で設定したcommentが表示されることを目視確認
  • 会員名称と最終ログイン日時と最終ログイン時の会員ステータス名称をログに出す
  • 最終ログイン日時が取得できてることをアサート

テストデータの登録時チェック

このような業務的な制約はどうしても存在してしまいます。 プログラム上では、不整合なデータが発生しないように気をつけて実装テストされますが、テストデータ自体の作成でのチェックは意外に忘れられがちです。

DBFluteクライアント/playsql配下の 20-member.xls にて、とある有効終了日が一つ後の有効期間の有効開始日を超える日 を指定して、ReplaceSchemaを実行してみましょう。 当然のことながら、正常に登録されてしまうはずです。

今度は、以下のSQLをDBFluteクライアント/playsql/take-finally.sql に記述して再実行してみましょう。ログに注目です。じっくり読んでみましょう。

e.g. 会員住所情報で同時点で住所が重複しないようにチェック @take-finally.sql
-- #df:assertListZero#
-- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- Member addresses should be only one at any time.
-- - - - - - - - - - -/
select adr.MEMBER_ADDRESS_ID, adr.MEMBER_ID
     , adr.VALID_BEGIN_DATE, adr.VALID_END_DATE
     , adr.ADDRESS
  from MEMBER_ADDRESS adr
 where exists (select subadr.MEMBER_ADDRESS_ID
                 from MEMBER_ADDRESS subadr
                where subadr.MEMBER_ID = adr.MEMBER_ID
                  and subadr.VALID_BEGIN_DATE > adr.VALID_BEGIN_DATE
                  and subadr.VALID_BEGIN_DATE < adr.VALID_END_DATE
       )
;

さらにチェック

それでは、チェックするSQLを作成して、実際にチェックしてみましょう。

まず、そのチェックに引っ掛かるようなデータに修正してください。 そして、以下をアサートするSQLを書いて、take-finally.sql に登録し、ReplaceSchemaでチェックされることを確認しましょう。データを元に戻し、再度 ReplaceSchema を実行して正常なデータが入ったことを確信しましょう。

  • 正式会員日時を持ってる仮会員がいないこと
  • まだ生まれていない会員がいないこと
  • 退会会員が退会情報を持っていることをアサート

ConditionBeanスタイルで

もし、よければ、(強いこだわりがなければ) ConditionBeanスタイルで書いてみましょう。 DBFluteユーザーが見慣れているフォーマットで書くことで、全体の可読性向上が期待できます。

e.g. "|" が左端と想定して、ConditionBeanスタイルの法則 @SQL
 |select ...
 |     , ...
 |  from MEMBER
 |    left outer join ...
 |    inner join ...
 | where ...
 |   and ...
 | order by ...

おまけチェック

実は、先ほどの会員住所情報の重複チェックのSQLは、有効期間が一日だけ重複しているパターンを検出することはできません。 時間が余った場合は、このSQLをどのように直せばそのパターンが検出できるのか試してみましょう。 また、様々なパターンに挑戦してみるのも良いでしょう。

SQLのエリアス名のこだわり

なかなか議論が尽きないところではありますが、SQLのエリアス名の付け方、こういった考えをもっています。 これといった正解をだしづらいものですが、ぜひいちどお読みくださいませ。

次のセクション

さて、次のセクションへ