ハンズオンセクション 6

概要

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

"デバッグログ設定と別名(和名)の利用" を学んでいきましょう。

事前準備

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

デバッグログ設定 (Lasta Di なら)

DIコンテナが Lasta Di であれば、logback になっています。

既に何度もデバッグログにはお世話になっているかと思います。 Eclipse (or IntelliJ) のコンソールにはDBFluteの検索処理のログが出力されていたかと思います。 これは、既に src/main/resources 配下に logback.xml が(最初から)配置されているからです。ここでは簡単ではありますが、この設定ファイルを修正すると出力されるログが変更されることを学びましょう。

試しに、logback.xml の org.dbflute の logger タグをコメントアウトして、今まで作った任意のテストを実行し、コンソールのログを確認してみてください。変化があるはずです。 変化を確認したら、元に戻してもう一度テストを実行して確認しましょう。(元に戻さないとログが出なくなっちゃいますよぅ)

デバッグログ設定 (Seasarのままなら)

DIコンテナが Seasar のままであれば、logj4 になっています。

既に何度もデバッグログにはお世話になっているかと思います。 Eclipse (or IntelliJ) のコンソールにはDBFluteの検索処理のログが出力されていたかと思います。 これは、既に src/test/resources 配下に log4j.properties (もしくは、src/main/resources に logback.xml) が(最初から)配置されているからです。ここでは簡単ではありますが、この設定ファイルを修正すると出力されるログが変更されることを学びましょう。

試しに、log4j.properties の log4j.logger.org.seasar を "#" でコメントアウトして、今まで作った任意のテストを実行し、コンソールのログを確認してみてください。変化があるはずです。 変化を確認したら、元に戻してもう一度テストを実行して確認しましょう。

また、log4j.appender.console.layout.ConversionPattern を "%d [%t]-%-5p - %m%n" に変更してみてください。変化を確認したら、元に戻してもう一度テストを実行して確認しましょう。

別名(和名)の利用 (+ Logic作成)

DBコメントの確認

replace-schema-10-basic.sql をご覧ください。 たくさんのDBコメントが定義されています。そして、DBコメントには、そのテーブルやカラムの別名(和名)と説明が "別名 : 説明" という形式で記載されています。まず、SchemaHTML を開いて、DBコメントがその形式であることを確認してください。(全てのコメントが必ずこの形式とは限りません)

ロジックを作りながら確認

それでは唐突ですが、JavaDoc上にDBコメントがあるかどうかを確認するために実装してみましょう。 以下のロジックを作成してテストしてみてください。実装しながら、それぞれのカラムに対応するメソッドの JavaDoc コメントを確認してみてください。特に別名は表示されていないはずです。

ロジックのメソッド
List<Member> selectSuffixMemberList(String suffix)
  • 指定された suffix で会員名称を後方一致検索
  • 会員名称の昇順で並べる
  • suffixが無効な値なら例外: IllegalArgumentException
  • 会員名称、生年月日、正式会員日時をログに出す (Slf4j or CommonsLogging)
  • そのログのログレベル、INFO/DEBUGどちらがいいか考えて実装してみましょう (この先ずっと同じ)
  • このメソッドは、他の人が呼び出すことを想定して public にしましょう (この先ずっと同じ)
対応テストメソッド
test_selectSuffixMemberList_指定したsuffixで検索されること()
  • suffix は "vic" で
  • テストメソッド名通りのアサート
  • テストが成り立っていることも(できる範囲で)アサート (今後ずっとそう)
test_selectSuffixMemberList_suffixが無効な値なら例外が発生すること()
  • 無効な値とは、nullと空文字とトリムして空文字になる値

ロジックの作り方はこのように

ちなみに、今回からエクササイズをより実践に近いもので行います。

  1. ロジッククラスにメソッドを定義して実装 (BehaviorはDIを利用)
  2. (Eclipseであれば) ctrl + 9 で対応するテストクラスを自動生成 (src/test/java に)
  3. (IntelliJであれば) shift + command + T で対応するテストクラスを自動生成 (src/test/java に)
  4. テストメソッドを実装してロジックを検証

ロジッククラスでの Behavior の宣言は、以下のようにします。(講師によるDIのお話あり)

e.g. ロジッククラスでの Behavior の宣言 @Java
/**
 * @author jflute
 */
public class HandsOn06Logic {

    @Resource
    private MemberBhv memberBhv;

    ...
}

テストクラスでのロジックの利用方法は、以下のようにします。

e.g. テストクラスでロジッククラスを生成して利用 @Java
public void test_...() {
    // ## Arrange ##
    HandsOn06Logic logic = new HandsOn06Logic();
    inject(logic);
    
    // ## Act ##
    ... = logic.select...();
    ...
}

DBFluteハンズオンでは、UTFluteを使っています。

例外メッセージを意識してみよう

本番環境で実行させる想定の src/main/java のクラスを作るようにもなってきましたし、例外ハンドリングもしっかりとデバッグ時を意識しながら実装しましょう。

ぜひ、こちらのブログもお読みください。

デバッグ時に嬉しいメッセージってどんなメッセージ? を考え続けることが大切です。

DBFluteプロパティの設定

DBMSには別名を定義する領域が存在しないため、DBコメントに無理矢理含めています。 なので、そのままだと、別名を別名して扱うことはできず、あくまでDBコメントの文字列の一部という扱いしかできません。 そこで...

それでは、ドキュメントに関するDBFluteプロパティ、DBFluteクライアントの dfprop/documentMap.dfprop を開いて、以下のプロパティを設定してみましょう。

aliasDelimiterInDbComment
DBコメント内の別名と説明の区切り(デリミタ)を表す文字
設定する値 => : (コロン)
isDbCommentOnAliasBasis
DBコメントの基本が別名か否か (デリミタがない場合にそれは別名なのかどうか)
設定する値 => true

dfpropのひな形コメントを使おう

よく使われるプロパティは、dfpropファイルの中にすでにコメント付きのひな形があります。 それをそのまま活用しましょう。

e.g. コメント付きのDBFluteプロパティ(aliasDelimiterInDbComment)のひな形 @documentMap.dfprop
    # /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # o aliasDelimiterInDbComment: (NotRequired - Default '')
    #  If the alias exists in its DB comment like as follows:
    #    member name : The name of member's full name
    #  you can use the alias in DBFlute world, java-doc, SchemaHTML...
    #  DB comment which does not have the delimiter is not treated
    #  as alias, treated as description (real comment).
    #  But you can change it by 'isDbCommentOnAliasBasis'.
    #
    #; aliasDelimiterInDbComment = : // ここのコメントを外して利用しましょう
    # - - - - - - - - - -/

再自動生成して確認

DBFluteプロパティの設定が終わったら、Docタスク、そして、Generateタスクを実行してみてください。 SchemaHTML、および、それぞれのカラムに対応するメソッドの JavaDoc コメントに変化があるはずです。

メソッド補完時のJavaDoc表示

IntelliJのみなさん、デフォルトではメソッド補完時に JavaDoc は表示されないので、 補完中に control+J を押してみてください。これをぜひ習慣化しましょう。

Eclipseの場合は、デフォルトでメソッド補完時に JavaDoc がぴょいぴょいでますので、無視せずに着目しましょう。有益な情報が入っているかもしれませんよ。

getであんまり検索したくない

ハンズオンでは、Logicの検索メソッドの名前は、select...() になっています。データを取得するからget?ではありません。 検索している、探している、ということを意識したいのです。

こういった考えでやっていますので、ぜひいちどお読みくださいませ。

DBFluteプロパティあれこれ

ちょっと大文字にしてみましょうか

ハンズオンのMySQLでは、SQL上で大文字小文字を区別しないで済むような設定がされています。

内部的には、テーブル名を全て小文字で管理することによって実現されています。 よって、DBFluteが取得するメタデータなどが小文字になっています(SchemaHTMLやCBで実行されたSQLなど)。 これを、大文字にするという地味な機能が用意されています。以下のプロパティ(littleAdjustmentMap.dfprop)を設定して再自動生成して確認してみてください。

e.g. DBFlute管理上ではテーブル名を大文字に @littleAdjustmentMap.dfprop
; isTableDispNameUpperCase = true
e.g. DBFluteで発行するSQLのテーブル名やカラム名を大文字に @littleAdjustmentMap.dfprop
; isTableSqlNameUpperCase = true
; isColumnSqlNameUpperCase = true

そもそもテーブル名やカラム名を大文字とするか小文字とするかは、文化や組織によって異なるものです。 どちらが良いという話ではなく、ERDなどのドキュメントと合わせるための地味機能と捉えてください。 (とはいえ、小文字にするプロパティは用意されてないのですが...)

ハンズオンでは、これらプロパティを true に設定して、再自動生成してそれぞれ大文字になっていることを確認してみましょう。

空文字をDBに入れたくない

DB上の空文字は、地味にトラブルを引き起こす可能性があります。

次のセクションから、登録や更新処理を行います。 この時点で、DBに空文字が入らないように施策を打っておきます。 以下のプロパティ (littleAdjustmentMap.dfprop) を設定して再自動生成しておきましょう。

e.g. Entityにセットされた空文字をnullに変換 @littleAdjustmentMap.dfprop
; isEntityConvertEmptyStringToNull = true

特に動作確認のためのエクササイズは用意していませんが、自動生成されたクラスの差分を見て、何が変わったのかだけ確認しておきましょう。 (非常に単純な実装になっています)

DBFluteを最新版にしよう

利用バージョンの確認

まず、自分がハンズオンで利用しているDBFluteのバージョンを確認してみましょう。

バージョンの確認
DBFlute Engine のディレクトリ名 (mydbflute/dbflute-1.x.x)

※pom.xml に定義されている DBFlute Runtime と、DBFlute Engine のバージョンは、設定ミスなどをしていなければ、この時点で必ず一致しているはずです。(一致していなければ、そもそも問題です)

最新版バージョンの確認

オフィシャルページを見てみましょう。

もしバージョンが古ければ

もし、(パッチ関係なく)バージョンが古ければ、アップグレードしてみましょう。

すでに最新だけどパッチがあれば

すでに最新バージョンを使っていても、パッチが存在していれば、パッチを当ててみましょう。

こういった環境的な作業も、DBFluteを使う上で大事なエクササイズです。

DBコメントを充実させましょう!

デコメントを知ろう

まず、こちらのページをじっくりお読みください。

もし、DBFluteのバージョンが古ければ(1.1.6より前だったら)、いますぐ最新にしてみましょう。

デコメントしてみよう

それでは、Intro経由で、いくつかのテーブルやカラムに対して すごく適当に デコメントしてみましょう。 また、同時にブラウザを二つ (タブを二つでもOKかも) 起動して、同じカラムのコメントを修正してコンフリクトも発生させてみてください。

想定通り、pieceファイルが作成されていることを確認しましょう。 (もし、git環境があればコミットしてみましょう。実運用では、それぞれのトピックブランチでのコミットを想定しています)

また、この時点では、ブラウザで直接 SchemaHTML を開いたときは、今回修正・追加したデコメントはまだ反映されていません。 それも確認しておきましょう。

Docを叩いてピックアップ

今度は、Docタスクを叩いてみましょう。

想定通り、pieceファイルは削除され、pickupファイルが作成されていることを確認しましょう。 (もし、git環境があればコミットしてみましょう。実運用では、DB変更専用のブランチでのコミットを想定しています)

そして、ブラウザで直接 SchemaHTML を開いて、反映されていることを確認しましょう。

コンフリクトを直すデコメントを

最後に、コンフリクトを直すデコメントをしておきましょう。 (もし、git環境があれば、これもコミット)

本気のデコメントをしてみましょう (三つ)

さて、あなたが... こう書いてあったらいいのに! と思う、価値のあるDBコメントを三つ デコメントで登録してみましょう。 (もし、git環境があれば、これもコミット)

何が書いてあったらディベロッパー(自分)は嬉しいのか?じっくり考えてみましょう。

どんなDBコメントを書いたらいいの?

ぜひブログを参考に。

このように、SchemaHTML経由 (Intro経由) で、DBコメントを "みんな" で追加できますので、実務でもどんどん使っていきましょう。

DB設計のレビューを自動化しよう!

SchemaPolicyCheckを知ろう

まず、こちらのページをじっくりお読みください。

エクササイズとして、実際にDB設計ポリシーを作って、チェックしてみましょう。

schemaPolicyMap.dfpropの作成

まず、[DBFluteクライアント]/dfprop配下に、schemaPolicyMap.dfprop という名前のテキストファイル (UTF-8) を作成します。

そして、以下の内容をまるごとコピーして、schemaPolicyMap.dfprop に貼り付けましょう。

ハンズオンで使う schemaPolicyMap.dfprop のひながた @schemaPolicyMap.dfprop
# /---------------------------------------------------------------------------
# schemaPolicyMap: (Default map:{})
#
# The definition of schema policy.
# You can check your schema as the policy.
# The table and column names are treated as case insensitive.
#
# Example:
# map:{
#     ; tableExceptList = list:{}
#     ; tableTargetList = list:{}
#     ; columnExceptMap = map:{}
#     ; isMainSchemaOnly = false
#     
#     ; wholeMap = map:{
#         ; themeList = list:{ uniqueTableAlias ; sameColumnAliasIfSameColumnName }
#     }
#     ; tableMap = map:{
#         ; themeList = list:{ hasPK ; upperCaseBasis ; identityIfPureIDPK }
#         ; statementList = list:{
#             ; if tableName is $$ALL$$ then fkName is prefix:FK_$$table$$
#         }
#     }
#     ; columnMap = map:{
#         ; themeList = list:{ upperCaseBasis }
#         ; statementList = list:{
#             ; if columnName is suffix:_FLAG then bad
#             ; if columnName is suffix:_FLG then notNull
#             ; if columnName is suffix:_FLG then dbType is INTEGER 
#             ; if columnName is suffix:_FLG then classification 
#         }
#     }
# }
#
# *The line that starts with '#' means comment-out.
#
map:{
    ; tableExceptList = list:{}
    ; tableTargetList = list:{}
    ; columnExceptMap = map:{}
    ; isMainSchemaOnly = false

    ; wholeMap = map:{
        ; themeList = list:{}
    }
    ; tableMap = map:{
        ; themeList = list:{}
        ; statementList = list:{
        }
    }
    ; columnMap = map:{
        ; themeList = list:{}
        ; statementList = list:{
            ; if columnName is suffix:_FLG then notNull
        }
    }
}
# ----------------/

ポリシーが一つだけ設定されています: "フラグカラムにはNotNull制約がつけよう"

わざとチェックに引っかかってみましょう

まず、Docタスク (manage の 22) を実行してみてください。maihamadbには、NotNull制約の付いていない _FLG のカラムはありませんので、問題なく成功します。ログを見て、チェックが有効になっていることだけを確認してみましょう。

そして、[DBFluteクライアント]/playsql/replace-schema-10-basic.sqlファイルを 一時的に 修正して、PURCHASEテーブルの PAYMENT_COMPLETE_FLGカラムの NotNull制約を外し、ReplaceSchema (manage の 0) を実行します。 (この時点で、DB上でNotNull制約が外れた状態になります)

PAYMENT_COMPLETE_FLG の NotNull制約を外す @SQL
PAYMENT_COMPLETE_FLG INTEGER NOT NULL COMME...,
  |
  v
PAYMENT_COMPLETE_FLG INTEGER COMME...,

その状態で、JDBCタスク (manage の 21) と Docタスク (manage の 22) を実行してみましょう。 (Docタスクは、DBを直接見るのではなく、抽出されたメタデータを見るので、ReplaceSchemaの後はJDBCが必要です)

Docタスクにて例外が発生し、SchemaPolicyCheckの violation がログに表示されているはずなので、確認してみましょう。

元に戻しましょう

確認できたら、replace-schema-10-basic.sqlを元に戻して、ReplaceSchema, JDBC, Docタスクを実行して、また正常に終了することを確認しましょう。

本気のポリシーを作ってみましょう

さて、あなたが... これはチェックしたいなぁ と思う、価値のあるポリシーを三つ 定義してみましょう。 そのポリシーの背景、理由、思いなど、ぜひ一緒にコメント (or 補足コメント) で書いておきましょう。

もし、ハンズオンで使っている maihamadb がそのポリシーに合わないようであれば、firstDateを使って新規テーブルだけに適用されるようにしてみましょう。

既存のDBでもうまく調整してフィットさせることができます。ぜひ現場でも活用していきましょう。

firstDateが効かない!?場合は

DBFlute-1.1.8 patch 2018/06/23

ハンズオンで利用している DBFlute Engine が、セクション1の時点でこのバージョンよりも古い場合は、firstDateを使っても既存テーブルが対象になってしまいます。 厳密には、一番の最初の自動生成時のテーブルが対象になってしまいます。テーブル作成履歴が存在しないためです。 (それ以降に作成したテーブルであれば対象外になりますが、ハンズオンのこの時点ではテーブルを作成するエクササイズは一つもありません)

この時点では、同セクションのアップグレードのエクササイズをやっているはずなので、このバージョンよりも新しくなっているはずです。 (不安であれば、念のため確認してみましょう。最新になっていなければ、そのエクササイズを先にやりましょう)

もし、HistoryHTML が存在しない、もしくは、HistoryHTML が存在していたとしても、セクション1で作成したときのテーブル追加の履歴がない場合は、履歴を再作成してみましょう。

以下のファイルを削除して、JDBCタスクならびにDocタスクを実行してみてください。

SchemaXML
DBFlute Client の schema/project-schema-maihamadb.xml
HistoryDiffMap
DBFlute Client の schema/project-history-maihamadb.diffmap

HistoryHTMLを見てみてください。テーブル作成の履歴が追加されているはずです。 ただ、セクション1の時点とセクション6の時点で日付が離れている場合は、HistoryHTMLの日付は厳密には間違いとなります。 気持ち悪いと思う場合は、project-history-maihamadb.diffmap を直接テキストエディターで開いて日付部分を修正してDocタスクを実行し直せば、つじつまを合わせることができます。

さて、この履歴の日付がテーブル作成日付ということで、firstDateでの比較日付になりますので、それに合わせて条件を設定すると良いでしょう。

※一つ注意、ハンズオンではこの時点で、"履歴が存在していない、もしくは、大した履歴じゃない" ということで、無理やり履歴を作成し直しましたが、実務ですでに価値のある履歴が存在している場合は、この方法は利用できません。 ただ、少し大変かもしれませんが、diffmapを直接修正することでつじつまを合わせることはできるので、どうしても必要になったら試してみると良いでしょう。 (ただし、diffmapを壊さないように気をつけましょう)

WebアプリでのDBFluteを知ろう

さて、だいぶ DBFlute の実装に慣れてきたところで...

  • Webアプリの中でDBFluteがどのように活用されるのか?
  • Webフレームワークと連携するDBFluteの機能とは?

これらに着目して、Webアプリのクラスを実装しながら DBFluteに触れてみましょう。 "サーバーサイドHTML" と "JSON API" のActionをそれぞれ作って実際に動かしてみましょう。(なぞるだけのハンズオンなので、難易度は低いです)

サーバーサイドHTML
Actionの作り方 (HTMLスタイル)
JSON API
Actionの作り方 (JSONスタイル)

業務で使うWebフレームワークと違っていても、DBFluteの役割を学ぶことがここでの大きな目的です。 他のフレームワークでも、ちゃんと連携すれば同じことができる ということを知り、自身のプロジェクトでうまく応用しておきましょう。

DBFluteのハンズオンのプロジェクトを修正するものではないので、どこにcloneするか?どこにimportするか?など環境は自由です。 単にcloneするだけでもいいですし、forkしても良いでしょう。 (Eclipseであれば)DBFluteハンズオンと同じworkspaceに入れても良いでしょう。

終わったら、セクション6のLogicクラスのどこかに、doneしたことがわかるようにコメントしておきましょう。 その中で、DBFluteのどんな機能が印象に残ったか?書いて頂けると嬉しいです。

次のセクション

設定系のエクササイズも飛ばさず学んでみたでしょうか?

さて、次のセクションへ