DBFluteのJava8版というか1.1ではこうなる

さて、最近のリリースでは、
「Java8に向けたポテンシャルの増強」
みたいなことやっていますが、
だいぶ方向性が固まってきたのでまとめてみたいと。

1.0.x -> Java6コンパイル, Java6,7,8で利用可能
1.1   -> Java8コンパイル, Java8以降のみ利用可能

ややこしいですが、1.0.x でもJava8で動かすことはできますが、
Java8に最適化はされていません。
一方で、1.0.x に既に Java8 というか 1.1 を意識した機能を
取り込んでいるので、Java6,7でも利用できる機能があります。

互換性は崩しますが、
基本的にオプション(dfprop)で戻せるようにはしています。
また、メソッド名などの変更は、
利用頻度の少ないものに限っています。

オプションは、
基本的に littleAdjustmentMap.dfprop です。

OptionalはselectEntity()とget[Relation]()

一番インパクトの多い Optional ですが、

o selectEntity()の戻り値
 -> selectByPK() や selectByUniqueOf() も含む

o setupSelect後のentity.get[Relation]()
 -> すべての many-to-one, one-to-one に

いろいろ考えていた昔の記事:
 -> DBFluteでのJava8妄想、やっぱりOptional
MemberCB cb = new MemberCB();
cb.setupSelect_MemberWithdrawalAsOne();
cb.acceptPrimaryKey(1);
OptionalEntity<Member> optMember
        = memberBhv.selectEntity(cb);

// 会員は必ず存在してコールバック、万が一存在しなければ例外
optMember.alwaysPresent(mb -> {
    mb.getMemberWithdrawalAsOne().ifPresent(wdl -> {
        // 退会会員が存在したら実行される
    });
    // カラムのプロパティはそのまま
    String memberName = mb.getMemberName();
    Date birthdate = mb.getBirthdate();
});
Optional型の変数名をどうするかって悩みますね。。。
関連テーブルが存在するかどうか?
selectEntity()は以前から積極的でしたが、
RelationのEntityもOptionalにするという
ポリシーが新しいものです。

「関連テーブルが存在するかどうか?」

カージナリティや制約次第で存在したりしなかったりします。
実はここが、バグが多い部分でもあり、
リレーショナルデータベースにおける実装の
大きなポイントでもあります。

NotNullのFKカラムによるリレーションなど、
固定的に必ず存在すると言えそうな場面もありますが、
クエリー次第でやっぱり存在しない場合もありますので、
(というか、setupSelectしなければ存在しないし)
単純化のために全てのリレーションがOptionalです。

カラムのプロパティも、本質的にはOptionalであるべき
ところですが、周辺のライブラリやプログラムがOptionalに
なっていないと、orElse(null)をただ手続き的にやるだけ
みたいなことになりかねないなと。
まだ時間が掛かるんじゃないかということで様子見です。
例外メッセージ重視のOptional
Java8標準のOptionalは使っていません。
OptionalEntityという名前の独自のクラスです。
ただ、メソッド構成はほぼ同じです。
ifPresent() に似た required() が追加されています。

ifPresent() : なければ素通り、あればコールバック
required()  : なければ例外で、あればコールバック

この違いです。データベースのプログラミングにおいては、

「業務的には必ず存在するんだけど、
万が一ないときはわかりやすい例外になってほしい」

という場面がたくさんあります。
そういうものはそもそもOptionalじゃないという話もありますが、
それが引数や状況によって動的に変わるのがデータベース。
というか、開発中ならバグで、本番でデータ不備もありえます。

ということで、その万が一のときに素通りして後のプログラムで
変なエラーになるよりかは、その場ですぐにエラーになる方がいい。
そして、わかりやすいエラーメッセージが出る方がいい。

標準のOptionalには、存在しないときに例外の get() はありますが、
存在しないときに例外のコールバックハンドリングがなかったので、
それを required() という名前で追加しています。
(メソッド名がまだ若干迷っていますが...)

そして、get() や required() のときの例外メッセージは、
ConditionBean で発行された SQL がそのまま出てきます。
万が一のときに追跡しやすいようにしました。

ということで、既にJava6,7でも使おうと思えば使えます。
selectEntityWithDeletedCheck()は残る
こちらは残ります。
何気に、get()やrequired()やるくらいなら、
selectEntityWithDeletedCheck() を呼べばOKです。
まったく目新しくないやり方ですが、この方が楽だし可読性もいいです。
大げさな感じが逆に「絶対に存在するんだぜ!」っていう意志表示が
伝わります。

単に、このメソッドをなかなか使ってもらえないケースが多いので、
selectEntity()自身がOptionalになれば、
存在しないケースへの意識が高まるのかなと。
1.0.x系でも使えて、逆に1.1で元に戻せる
インターフェースのデフォルト実装がないので完璧ではないですが、
既に、1.0.xのDBFluteには備わっています。
littleAdjustmentMap.dfpropにて、

1.0.xで使いたいなら:

isAvailableSelectEntityPlainReturn = false
isAvailableRelationPlainEntity = false

※その他の対応もOKなら、
 isCompatibleBeforeJava8 = false
だけOKです。

1.1 で元に戻したいなら:

isAvailableSelectEntityPlainReturn = true
isAvailableRelationPlainEntity = true

なので、1.0.x から 1.1 のアップグレードをする場合でも、
既存の実装にある程度合わせることができます。

LoadReferrerのネストテーブル対応のやり方

LoadReferrerOptionによるちょっとゴタゴタしたやり方を廃止し、
setupSelectのときのネストテーブルと同じように、
続けて withXxx() とできるようにしました。

そして、これは既に 1.0.x 系で導入されています。
 -> 子テーブルの子テーブルの取得 (one-to-many-to-many)
Java8だとこんな感じ
MemberCB cb = new MemberCB();
cb... // 様々な条件
List<Member> memberList = memberBhv.selectList(cb);

// 会員に加えて購入、そして、その子テーブルである購入支払を取得
memberBhv.loadPurchase(memberList, purchaseCB -> {
    purchaseCB.query().addOrderBy_PurchaseDatetime_Desc();
}).withNestedReferrer(refList -> {
    purchaseBhv.loadPurchasePaymentList(refList, paymentCB -> {
        paymentCB.query().addOrderBy_PaymentDatetime_Desc();
    });
});
withNestedReferrer()でお好きなように。
枝分かれの孫テーブルもOKです。
細かい互換
LoadReferrerOptionの方のメソッドは、
1.1からは生成されなくなります。
isCompatibleBeforeJava8 で元に戻すこともできますが、
そもそもそんなに利用ケースは多くないと考えられるので、
少し頑張って修正してしまう方がオススメです。

1.0.xでは、しばらく並行運用(SEっぽい言葉だなぁ)となります。
1.0.xでも、isCompatibleBeforeJava8 を false にすると、
LoadReferrerOptionの方のメソッドは生成されなくなります。

ちなみに、LoadReferrerのコールバック引数の、
ConditionBeanSetupperというインターフェースですが、
ReferrerConditionSetupperというインターフェースに変わります。
1.1 からそのようになりますが、
ただ、lambdaになってしまえばあまり意識することはないでしょう。
別にJava8関係なくない?
That's right です。
ですが、Java8版を作るにあたって、
色々と見直し、互換を少し崩しても改善をするからこそ、
こういうことを考え始めることができたという面があるので、
同じように取り扱っています。
【追記】Loader方式
さらに、たくさんの関連テーブルをLoadするとき、
こんな感じのやり方ができるようになります。

// DBFlute-1.0.5J Released
http://d.hatena.ne.jp/jflute/20140628/release105j
MemberCB cb = new MemberCB();
cb.query()...
List<Member> memberList = memberBhv.selectList(cb);

memberBhv.load(memberList, loader -> {
    // [one-to-many-to-many]
    // 会員から購入を取得、さらに、購入支払を取得
    loader.loadPurchaseList(purchaseCB -> {
        purchaseCB.query()...
    }).withNestedReferrer(loader -> {
        loader.loadPurchasePaymentList(paymentCB -> {
            paymentCB.query()...
        });
    });
    // [many-to-one-to-many]
    // 会員から会員ステータスを経由して、会員ログインを取得
    // (会員ステータスが setupSelect されていることが前提)
    loader.pulloutMemberStatus().loadMemberLoginList(loginCB -> {
        loginCB.query()...
    });
});

Java8のラムダの心配なところ対応

まあこちらです。
 -> Java8のラムダの心配なところ...

1.0.xではデフォルトにはしていません。
それはそれで正常に動いてしまっているケースもあるかもしれないので。
ただ、実装上あまり良いことではないので、

1.0.xでも有効にすることができます。
littleAdjustmentMap.dfpropにて、

isThatsBadTimingDetect = true

とすると、チェックが有効になります。
新しく 1.0.x を使う人は、true にしていいかと思います。

1.1で無効にするには逆にすればいいですが、
オススメではありません。
似たような話で OrScopeQuery
OrScopeQuery の中で、SetupSelect や OrderBy を
やっても特におとがめはありませんでした。
これは単なるDBFluteのチェック漏れです。
(SubQueryとかは厳密にチェックしているのに...)

こちらも、正常に動いてしまっているケースもあるかもなので、
1.0.xではデフォルトにはしませんが、

isCompatibleOrScopeQueryPurposeNoCheck = false

でチェックを有効にできます。
新しく 1.0.x を使う人は、true にしていいかと思います。

selectByPKValue() は selectByPK() に

細かい話ですが、
selectByPKValue()というメソッドは、
selectByPK()に変わります。

これはもう「ああ、名前やっちゃったなぁ」という
気持ちでいっぱいのメソッドでした。
Value要らないだろー。

1.0.xでも、
littleAdjustmentMap.dfpropにて、

isCompatibleSelectByPKOldStyle = false

とすれば、selectByPK()にできます。
新しく 1.0.x を使う人は、true にしていいかと思います。
1.1で互換を保ちたい人は逆にすればOKです。

また、selectByPKWithDeletedCheck()は生成しません。
ByPKの方は、シンプル性を重視したものということで、
selectByPK()の戻り値 Optional に任せます。
(もともと、setupSelectできないので利用頻度も少ないかと)

isCompatibleSelectByPKWithDeletedCheck = false

にすると、1.1でも WithDeletedCheck も生成されます。

one-to-oneへのExistsRefererrとか

今までは、one-to-oneへのExistsRefererrが
生成されていましたが、
QueryRelationを使えばいいのでほぼ不要でした。
ということで、1.1からは生成されなくなります。

isMakeConditionQueryExistsReferrerToOne = true

とやると、生成されるようになります。
1.0.xで生成したくない場合は false にすればOKです。

同じように InScopeRelation は、
many-to-one, one-to-oneに対しても生成されていましたが、
同じことが言えるので、1.1からは生成されなくなります。

isMakeConditionQueryInScopeRelationToOne = true

とやると、生成されるようになります。

基本的に、one-to-manyのReferrerに対してだけ、
メソッドが生成されるようになります。

withManualOrder()のリスト引数メソッド

今までは、以下のような二つのオーバーロードメソッドでした。

A. withManualOrder(List valueList)
B. withManualOrder(ManualOrderBean mob)

Aが消えます。

isMakeConditionQueryPlainListManualOrder = true

で、1.1で生成できます。
ManualOrderBeanでacceptOrderValueList()すれば
同じことができるので、シンプルさを優先しました。
(利用頻度の低いオーバーロードは無くしたい)


DBFluteランタイムのパッケージ

org.dbfluteにすると思います。

あまりランタイムのクラスを直接使う場面はないはずで、
使っていたとしても「インポートの編成」で一括で修正しやすいかと。
0.8.x から 0.9.0 になったときに似たようなことありましたが、
わりとすんなり移行してもらえました。

ということで、1.0.xでも...

1.0.xでも、これらの機能を積極的に活用していった方が、
将来的に論理的にも物理的にも移行しやすくなります。
1.0.x で Java6,7 で開発中プロジェクトなら...
コンパイルエラーがでなければ、設定してもOKかと。
(独自にリフレクションとかで呼び出してなければですが、考えにくい)
# littleAdjustmentMap.dfprop

map:{
    ; isCompatibleSelectByPKOldStyle = false
    ; isCompatibleSelectByPKPlainReturn = false
    ; isCompatibleSelectByPKWithDeletedCheck = false
    ; isMakeConditionQueryExistsReferrerToOne = false
    ; isMakeConditionQueryInScopeRelationToOne = false
    ; isMakeConditionQueryPlainListManualOrder = false

    # /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # o isAvailableAddingSchemaToTableSqlName: (NotRequ...
    ...
}
ちょっとエラーになっても、そんなに多くなければ直してしまえばと。

1.0.5H から isCompatibleSelectByPKPlainReturn が追加されています。
selectByPK()だけ OptionalEntity にすることができます。
(selectEntity()はキープしたまんま)
1.0.x で Java6,7 で新しいプロジェクトなら...
実行時チェックの機能も入れていいかと思います。
# littleAdjustmentMap.dfprop

map:{
    ; isCompatibleSelectByPKOldStyle = false
    ; isCompatibleSelectByPKPlainReturn = false
    ; isCompatibleSelectByPKWithDeletedCheck = false
    ; isMakeConditionQueryExistsReferrerToOne = false
    ; isMakeConditionQueryInScopeRelationToOne = false
    ; isMakeConditionQueryPlainListManualOrder = false
    ; isThatsBadTimingDetect = true
    ; isCompatibleOrScopeQueryPurposeNoCheck = true

    # /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # o isAvailableAddingSchemaToTableSqlName: (NotRequ...
    ...
}
isThatsBadTimingDetectと、
isCompatibleOrScopeQueryPurposeNoCheckが、
実行時チェックなので、新しいプロジェクトなら。
まあ、テスト前であれば開発中プロジェクトで使ってもいいかと。
1.0.x で Java8 なら...
isCompatibleBeforeJava8 = false

とだけ設定すれば、もろもろの細かい設定が1.1用になり、
Optional も有効になります。

まだ悩んでるのも

まだ悩んでるのもあります。
デフォルトを true or false?

デフォルトfalseだが、ほとんどtrueでOKのプロパティ

こちらの設定、どうしようか迷っています。
区分値周りとか、デフォルトでtrueにしてもいいかなぁとか。

あと、

cb.query().setXxx_Equal(null);
cb.query().setXxx_InScope(空リスト);

で、条件が消えるやつ。

この挙動は、画面の検索にとても相性がいいですが、
そうでない場面で間違えてnullが来て条件が消えるやつ。

いまだと、
cb.checkInvalidQuery();
で、例外にすることができます。

この設定を逆にしてデフォルトでは例外で、
cb.acceptInvalidQuery();
とすると逆に条件がなくなるようにするとか。

全件検索事件を、三年に一回くらい耳にするんですよね。
メソッドの先頭でちゃんと引数チェックをしてほしい、
という気持ちはありながらも、
悩みですねぇ。。。

ボツになった(なる予定!?)のもあります

setupSelect_Xxx()
 -> selectWith_Xxx();

「基点テーブルに対して関連テーブルも一緒の取りたい」
という意味を強くするために。
新しい人たちに使ってもらうことを考えたときに、
もう少し直感的な名前にした方がいいかなと思って。

でも、これはあまりにインパクトが大きすぎるかと。
オプションで互換性は保つにしても、
精神的な互換性があまりに崩れてしまいます。
そこまでやるなら、他にもやった方がいいものもありそうだし。

そしてそして、
「ConditionBean で LoadReferrer できるようにする」
ということも考えましたが、
確実に実装しやすくはなりますが、
selectList(cb);のSQL発行回数が見た目からわかりにくくなり、
DBFluteポリシーに若干反してるところがあるので。
ベタベタでわかりやすいってのがDBFluteらしさでもあると。

これらは悩みました。まあ今も悩んでますけど...
これらはボツになる予定です。

でも、またきっかけがあれば復活するかもしれませんので、
まあ、アイディアとしては心に留めておくという感じです。

とりあえずはこんなかんじ

とりあえずはこのようなところです。
まだ、Java8版を作ってないので、
実際に作り始めたらまたもうちょい色々と出てくるかもしれません。

確定的なものをまとめさせて頂きました。
意見あれば、遠慮なく聞かせてください。
これからもDBFluteをよろしくお願いします。

【追記】さらにいろいろと増えました

// DBFlute-1.1になったらStrict
http://d.hatena.ne.jp/jflute/20140813/strict11

// DBFlute-1.1の区分値強制のお話
http://d.hatena.ne.jp/jflute/20140823/forcecls

// DBFlute-1.1ではSQLにコメントを
http://d.hatena.ne.jp/jflute/20140824/sqlcomment

// DBFlute-1.1のDerivedReferrer
http://d.hatena.ne.jp/jflute/20140828/derived

// DBFlute-1.1の日付型 Time API
http://d.hatena.ne.jp/jflute/20140829/localdate

// DBFlute-1.1に近づくオプションたち
http://d.hatena.ne.jp/jflute/20140830/to11

// jflute と Seasar
http://d.hatena.ne.jp/jflute/20140925/seasar

// DBFlute-1.1でLambdaでCB
http://d.hatena.ne.jp/jflute/20141001/cblambda

// DBFlute-1.1のReferrerメソッド微調整
http://d.hatena.ne.jp/jflute/20141011/referrerwish

// DBFlute-1.1からは slf4j を使う
http://d.hatena.ne.jp/jflute/20141020/slf4j

// DBFlute-1.1からはNonSpecifiedチェック
http://d.hatena.ne.jp/jflute/20141021/nonspecified

そして、Java8のためのDBFluteページ作りました!

// DBFlute on Java8
http://dbflute.seasar.org/ja/tutorial/onjava8.html