移行 1.0.x to 1.1

お約束の注意点

古いバージョンの削除
古いバージョンのDBFluteランタイム(JAR)が[WEB-INF/lib]の下などに残らないように
タスクを実行し忘れないように
Generateタスクだけでなく、(外だしSQLがある場合は)Sql2Entityタスクも実行

概要

1.0.x から 1.1 に移行するのは、かなりの強者(つわもの)です。

移行のレベルが大きく三つあります。

ひとまず 1.1 にするだけ
互換モードを全開にして、できるだけ修正を少なく移行
とにかく 1.1 に合わせる
互換モードをOFFにして、方式もすべて移行
その中間くらい
ひとまず 1.1 にして、簡単に移行できそうなものだけ移行

まずは、"ひとまず 1.1 にするだけ" を目標に、現実的に "その中間くらい" に短い時間でチャレンジしてキリのいいところで落ち着くのがよいでしょう。

互換モードも、すべての機能に対応しているわけではありません。 メジャーな機能は互換を保てるようにしていて、マイナー(と思われる)機能に関してはちょっと手を動かせば移行できるだろうということで、バランスをとっています。 また、互換を保っていない機能の多くが、コンパイルレベルで検知できるものになっています。

大前提

Java8であること
Java8でなければ、とりあえず1.0.xのままJava8へ移行してから
1.0.xの最新であること
1.0.xの最新版でなければ、まずは最新版へ移行してから
テストすること
特に運用中のシステムなら、しっかりシナリオテストすること
聞くこと
わからなければ、ML や Twitter などで遠慮なく jflute に聞くこと

DBFlute-1.1.xをセットアップ

DBFluteエンジンのダウンロード

1.1.xのDBFluteエンジンをダウンロードします。

manageタスクの upgrade (94) をバージョン指定で実行します。 Windowsであっても必ずコマンドラインから実行する必要があります。

e.g. manage にて、バージョン指定で upgrade @Command
...$ sh manage.sh 94 1.1.0

この時点では、自動生成はまだやりません。

DBFluteランタイムの準備

1.1.xのDBFluteランタイムを用意します。

e.g. DBFluteランタイム-1.1.0のdependencyを定義 @pom.xml
<dependencies>
    ...
    <dependency>
        <groupId>org.dbflute</groupId>
        <artifactId>dbflute-runtime</artifactId>
        <version>1.1.0</version>
    </dependency>
    ...
</dependencies>

この時点で、ことごとくコンパイルエラーになるはずです。

Log4jを使っていたらSlf4jのブリッジを

DBFlute-1.1.0から、DBFlute内部では commons-logging ではなく slf4j を使ってログを出力しています。なので、もし Log4j を使っている場合はそのままではログが出力されなくなるので、slf4j でのログ出力を log4j 経由にするブリッジライブラリを設定します。

詳しくはブログの通り。

dependencies だけでなく、logger設定の org.dbflute パッケージへの変更を忘れずに。

Slf4jを使っててもパッケージを修正

最初から Slf4j (LogBack) を使っていたとしても、やはりlogger設定の org.dbflute パッケージへの変更を忘れずに。

まず、互換モードを全開にする

littleAdjustmentの互換オプション

大きく二種類あります。

compile compatible
コンパイルレベルで移行できてるか検知できる
runtime compatible
アプリを実行しないと大丈夫かどうかわからないもの

厳密には、"compile compatible" の中には、sql2entity や outside-sql-test タスクを実行すれば検知できるものも含まれています。要は、アプリの実行前に検知できるものと考えるとよいでしょう。

以下の "赤い部分" を littleAdjustmentMap.dfprop にコピーして、manage の renewal をします。すべての互換オプションをONにしています。

e.g. 互換モード全開 @littleAdjustmentMap.dfprop
...
# o relationalNullObjectMap: (NotRequired - Default map:{})
#
# *The line that starts with '#' means comment-out.
#
map:{
    # _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    # Migration to Java8 DBFlute
    # http://d.hatena.ne.jp/jflute/20140530/java8
    # _/_/_/_/_/_/_/_/_/
    # compile compatible (true if compatible)
    ; isCompatibleSelectByPKOldStyle = true
    ; isCompatibleSelectByPKPlainReturn = true
    ; isCompatibleSelectByPKWithDeletedCheck = true
    ; isMakeConditionQueryExistsReferrerToOne = true
    ; isMakeConditionQueryInScopeRelationToOne = true
    ; isMakeConditionQueryPlainListManualOrder = true
    ; isMakeDirectConditionBeanSetup = true
    ; isMakeBatchUpdateSpecifyColumn = true
    ; isAvailableSelectEntityPlainReturn = true
    ; isAvailableRelationPlainEntity = true
    ; isMakeConditionQueryPrefixSearch = true
    ; isMakeConditionQueryDateFromTo = true
    ; isMakeDirectConditionOptionSetup = true
    ; isMakeDirectConditionManualOrder = true
    ; isMakeClassificationNativeTypeSetter = true
    ; isCompatibleNewMyEntityConditionBean = true
    ; isCompatibleDeleteNonstrictIgnoreDeleted = true
    ; isCompatibleLoadReferrerOldOption = true
    ; isCompatibleConditionBeanAcceptPKOldStyle = true
    ; isCompatibleConditionBeanOldNamingCheckInvalid = true
    ; isCompatibleConditionBeanOldNamingOption = true
    ; isCompatibleBizOneToOneImplicitReverseFkAllowed = true
    ; isCompatibleReferrerCBMethodIdentityNameListSuffix = true
    ; isCompatibleOutsideSqlFacadeChainOldStyle = true
    ; isCompatibleOutsideSqlSqlCommentCheckDefault = true
    ; isCompatibleSelectScalarOldName = true

    # compile compatible (false if compatible)
    ; isMakeCallbackConditionBeanSetup = false
    ; isEntityDerivedMappable = false
    ; isMakeCallbackConditionOptionSetup = false
    ; isMakeCallbackConditionManualOrder = false
    ; isAvailableJava8TimeLocalDateEntity = false

    # runtime compatible (true if compatible)
    ; isNullOrEmptyQueryAllowed = true
    ; isOverridingQueryAllowed = true
    ; isNonSpecifiedColumnAccessAllowed = true
    ; isCompatibleConditionBeanFromToOneSideAllowed = true
    ; isCompatibleOrScopeQueryPurposeNoCheck = true
    ; isSuppressDefaultCheckClassificationCode = true

    # runtime compatible (false if compatible)
    ; isThatsBadTimingDetect = false

    # /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # o isAvailableAddingSchemaToTableSqlName: (NotRequired - Default false)
    ...
}

互換モード全開で renewal

manage の renewal をします。

importの編成を一気に

自動生成クラス、アプリのクラス、もろもろすべてのクラスに対して、importの編成 (Eclipseにて) を実行します。org.seasar.dbfluteの参照が、org.dbfluteに "どかどか" 変わっていくかと思います。

クラス名だけでユニークに解決できないクラスは、個別個別にソースを開いて、ctrl+shift+O をやらないといけないかもしれません。 どのクラスが解決できていないのかはこの時点で判断できないので、次のステップへ進んで一緒にやっていきます。

互換のないコンパイルエラーを直す

importの編成をやってもコンパイルエラーになっているものは、互換性のない機能変更によるもの、もしくは、importが解決できなかったものです。

grepの一括置換でOKなやつ

Eclipseなら ctrl+H で一括置換してもよいものです。

ManualOrderBean を
ManualOrderOption に

ただし、アプリで全然違う機能の同名クラスを自作している場合はダメです(ありえないと思いますが)。

地道に直す

地道に直すものです。

DerivedReferrerOption
DerivedReferrer のオプション指定は Lambda に
ColumnConversionOption
ColumnQuery のオプション指定は Lambda に
ScalarSelectOption
ScalarSelect のオプション指定は Lambda に
ManualOrderOption
ManualOrderOption の when の FromTo 指定は Lambda に
PagingResultBean
pageRange()などの設定の仕方が Lambda に
DateFromTo
DateFromTo自体がなくなったので FromTo で代用
selectScalar()
戻り値が Optional に

実は、これは全部ではありません。その理由は二つ。

  • 通常、ユーザーが利用しないものは省略している
  • というか、そういった細かいものは、さすがに忘れてしまいました...ごめんなさい

アプリがDBFluteの内部的なクラスをどれだけさわっているかによります。 もし、どういう修正をすればよいのか判断の付かないコンパイルエラーがあったら、そこは遠慮なく jflute に聞いてください。

いったんテスト

コンパイルエラーが取れたら、アプリのテストをしてみましょう。

すんなり動けば、とりあえず 1.1 への移行は最低限できたことになります。 時間がなければ、ここで終わってもOKでしょう。

dfpropの移行

dfpropのコメントなど、1.1.xから変わっているものもあるので、間違い防止のためにも修正しておくとよいでしょう。

basicInfoのコメント

まずは、basicInfoMap.dfprop のヘッダーコメント、まるごとコピーして上書きしてしまいましょう。

e.g. basicInfoMap.dfprop のヘッダーコメント @basicInfoMap.dfprop
# /---------------------------------------------------------------------------
# basicInfoMap: (Required)
#
# The basic information for the tasks of DBFlute.
# You should specify before your first generating.
#
# Core Properties:
# o database: (Required)
# o targetLanguage: (Required)
# o targetContainer: (Required)
# o packageBase: (Required)
#
# Adjustment Properties:
# o generateOutputDirectory: (NotRequired - Default Java:'../src/main/java' CSharp:'../source')
# o resourceOutputDirectory: (NotRequired - Default '../resources')
# o isTableNameCamelCase: (NotRequired - Default false)
# o isColumnNameCamelCase: (NotRequired - Default false)
# o projectPrefix: (NotRequired - Default '')
# o classAuthor: (NotRequired - Default 'DBFlute(AutoGenerator)')
# o sourceFileEncoding: (NotRequired - Default 'UTF-8')
# o sourceCodeLineSeparator: (NotRequired - Default no setting)
# o applicationBehaviorMap: (NotRequired - Default map:{})
# o outputPackageAdjustmentMap: (NotRequired - Default map:{})
# o dbfluteSystemFinalTimeZone: (NotRequired - Default null)
#
# *The line that starts with '#' means comment-out.
#

classificationDefinitionのコメント

まずは、classificationDefinitionMap.dfprop のヘッダーコメント、まるごとコピーして上書きしてしまいましょう。

e.g. classificationDefinitionMap.dfprop のヘッダーコメント @classificationDefinitionMap.dfprop
# /---------------------------------------------------------------------------
# classificationDefinitionMap: (NotRequired - Default map:{})
#
# The definition of classification.
#
# Specification:
# map: {
#     [classification-name] = list:{
#         ; map:{
#             ; topComment=[comment]; codeType=[String(default) or Number or Boolean]}
#             ; undefinedHandlingType=[EXCEPTION or LOGGING(default) or ALLOWED]
#             ; isUseDocumentOnly=[true or false(default)]
#             ; isSuppressAutoDeploy=[true or false(default)]
#             ; groupingMap = map:{
#                 ; [group-name] = map:{
#                     ; groupComment=[comment]
#                     ; elementList=list:{[the list of classification element's name]}
#                 }
#             }
#         }
#         # classification elements for implicit classification
#         ; map:{
#             ; code=[code]; name=[name]; alias=[alias]; comment=[comment]
#             ; sisterCode=[code or code-list]; subItemMap=map:{[free-map]}
#         }
#         # settings for table classification
#         ; map:{
#             ; table=[table-name]
#             ; code=[column-name for code]; name=[column-name for name]
#             ; alias=[column-name for alias]; comment=[column-name for comment]}
#             ; where=[condition for select]; orderBy=[column-name for ordering]
#             ; exceptCodeList=[the list of except code]
#         }
#     }
# }
#
# *The line that starts with '#' means comment-out.
#

littleAdjustmentのコメント

まずは、littleAdjustmentMap.dfprop のヘッダーコメント、まるごとコピーして上書きしてしまいましょう。

e.g. littleAdjustmentMap.dfprop のヘッダーコメント @littleAdjustmentMap.dfprop
# /---------------------------------------------------------------------------
# littleAdjustmentMap: (NotRequired - Default map:{})
#
# The various settings about a little adjustment.
#
# o isAvailableAddingSchemaToTableSqlName: (NotRequired - Default false)
# o isAvailableAddingCatalogToTableSqlName: (NotRequired - Default false)
# o isAvailableDatabaseDependency: (NotRequired - Default false)
# o isAvailableDatabaseNativeJDBC: (NotRequired - Default false)
# o isAvailableNonPrimaryKeyWritable: (NotRequired - Default false)
# o classificationUndefinedHandlingType: (NotRequired - Default LOGGING)
# o isEntityConvertEmptyStringToNull: (NotRequired - Default false)
# o isMakeConditionQueryEqualEmptyString: (NotRequired - Default false)
# o isTableDispNameUpperCase: (NotRequired - Default false)
# o isTableSqlNameUpperCase: (NotRequired - Default false)
# o isColumnSqlNameUpperCase: (NotRequired - Default false)
# o isMakeDeprecated: (NotRequired - Default false)
# o isMakeRecentlyDeprecated: (NotRequired - Default true)
# o extendedDBFluteInitializerClass: (NotRequired - Default null)
# o extendedImplementedInvokerAssistantClass: (NotRequired - Default null)
# o extendedImplementedCommonColumnAutoSetupperClass: (NotRequired - Default null)
# o shortCharHandlingMode: (NotRequired - Default NONE)
# o quoteTableNameList: (NotRequired - Default list:{})
# o quoteColumnNameList: (NotRequired - Default list:{})
# o columnNullObjectMap: (NotRequired - Default map:{})
# o relationalNullObjectMap: (NotRequired - Default map:{})
# o cursorSelectFetchSize: (NotRequired - Default null)
#
# *The line that starts with '#' means comment-out.
#

outsideSqlDefinitionのコメント

outsideSqlDefinitionMap.dfprop は、isRequiredSqlTitle と isRequiredSqlDescription のデフォルト値が true に変わっているので、ヘッダーコメントと、プロパティ定義部分のコメントを修正しましょう。

追加されたプロパティは?

この状態では、追加されているプロパティの雛形はありませんが、使わないのであればそのままでもよいでしょう。 とりあえずヘッダーにさえあれば、それに気付いて設定しようとしたときに、mydbfluteのDBFluteクライアントの雛形からコピーして持ってくればOKです。

ちなみに、追加されているのは、dbfluteSystemFinalTimeZone や classificationUndefinedHandlingType, columnNullObjectMap などです。

がらっと変わった未定義区分値のプロパティ

未定義区分値のプロパティががらっと変わっています。 無理することはありませんが、もしよければ移行という感じで。

もう少し攻める

少ない時間でちょっとやれそうだったらやってみるとよいものです。

現実的なコンパイルレベルでの検知

まずは、true から false にして自動生成し直してみて、コンパイルエラーがでるかどうか? もし、エラーの数が少なく、すぐに直せそうなら直してしまいましょう。

一個ずつ false にして自動生成して直して、また次を一個 false にして...を繰り返すとよいでしょう。

isCompatibleSelectByPKOldStyle (to false)
selectByPKValue() から selectByPK() に
利用箇所は少ないと想定され、一括置換でも修正できそうなのでやれたらやってみるとよい
isMakeConditionQueryExistsReferrerToOne (to false)
one-to-one への ExistsReferrer を生成しないように
相手側PKによる IsNotNull もしくは IsNull で代用
isMakeConditionQueryInScopeRelationToOne (to false)
many-to-one への InScopeRelation を生成しないように
普通のQueryRelationによる条件付与で代用
isMakeConditionQueryPlainListManualOrder (to false)
ManualOrder の List 受け取りメソッドを生成しないように
ManualOrderOption の acceptOrderValueList() で代用
isMakeBatchUpdateSpecifyColumn (to false)
batchUpdate()のSpecifyするオーバーロードを生成しないように
そもそもbatchUpdate()がSetter呼び出し方式なので、プログラムを見て同じupdateになるならOK
もし、Setter呼び出しじゃないようにする互換オプションを使っていたら、ちょっとやめておきましょう
isMakeConditionQueryPrefixSearch (to false)
PrefixSearchを生成しないように、LikeSearchOption の likePrefix() で代用
isMakeConditionQueryDateFromTo (to false)
DateFromToを生成しないように、FromToOption の compareAsDate() で代用
isMakeClassificationNativeTypeSetter (to false)
区分値カラムのネイティヴ型引数のメソッドを作らないように
もともと isForced... を使っている場合は false にして問題ないはず
isCompatibleNewMyEntityConditionBean (to false)
Behavior の newMyCondition() が newConditionBean() が変わった (Entityも)
isCompatibleDeleteNonstrictIgnoreDeleted (to false)
Behavior の deleteNonstrictIgnoreDeleted() を生成しないように
try catch で代用
isCompatibleLoadReferrerOldOption (to false)
LoadReferrer の LoadReferrerOption を使うオーバーロードメソッドを生成しないように
ネストした LoadReferrer は、loader方式で代用
isCompatibleConditionBeanAcceptPKOldStyle (to false)
cb.acceptPrimaryKey() が cb.acceptPK() に変わった
isCompatibleConditionBeanOldNamingCheckInvalid (to false)
cb.checkInvalidQuery() が cb.checkNullOrEmptyQuery() に変わった
isCompatibleConditionBeanOldNamingOption (to false)
cb の細かいオプションたちのメソッド名が cb.enableXxx() or cb.disableXxx() に変わった
isCompatibleBizOneToOneImplicitReverseFkAllowed (to false)
暗黙の逆参照FKを生成しないように、もし必要なら普通に additionalForeignKey で定義
isCompatibleReferrerCBMethodIdentityNameListSuffix (to false)
例えば、existsPurchaseList() が existsPurchase() に、だいぶスッキリします
isCompatibleOutsideSqlFacadeChainOldStyle (to false)
例えば、outsideSql().cursorHandling().selectCursor() が outsideSql().selectCursor() に
TypedParameterBeanじゃないものは、outsideSql().traditionalStyle().select... に
外だしSQLの数次第で、移行が現実的かどうか決まるかと
isCompatibleOutsideSqlSqlCommentCheckDefault (to false)
外だしSQLのコメントを必須に(OutsideSqlTestなどでチェック)、こちらも外だしSQLの数次第
isCompatibleSelectScalarOldName (to false)
Behaviorのメソッド、scalarSelect() ではなく selectScalar()に

逆に false から true にするやつは、こちらです。

isEntityDerivedMappable (to true)
DerivedReferrer で derived() が使えるようにする
別に互換を損なうことはないので、問答無用で true にしてしまってもいいかと
アプリのポリシーとして、いまさらこっちは使わないとかであればそのままで

現実的な実行レベルでの検知

実行しないとわからないものはそもそも現実的ではないのですが、それでもまあいけるんじゃないかな? っていうのと、あとそんな実装があったらそもそも困るみたいなものです。 開発中なのか、既に運用中なのかでだいぶニュアンスが変わってくると思います。

true から false にして自動生成し直してみて、テストしてみましょう。 あとで、一括テストするのであれば、とりあえず設定しておくだけでもいいかもです。

isNonSpecifiedColumnAccessAllowed (to false)
SpecifyColumnしたのに、Specifyされていないカラムをgetするのを許す
そもそもバグかもしれないので false に。どうしても必要なら、cb.disableNonSpec...()
isCompatibleOrScopeQueryPurposeNoCheck (to false)
OrScopeQueryのコールバックの中でSetupSelectやOrderByしているのを許す
許したくないので false に
isSuppressDefaultCheckClassificationCode (to false)
区分値に定義されているもの以外の区分値をsetしたりgetしたりを許す
許したくないので false に (隠れ区分値がなければ)

そして、逆に false から true にするやつです。

isThatsBadTimingDetect (to true)
例えば、ExistsReferrerのコールバックの中で外側の cb を呼び出してるとか
Lambdaになるとこの間違いによるバグが増えそうなので、ぜひ true にしたい

チャレンジな世界へ...はちょい待って

Optional や Lambda への移行は大変

残ったプロパティは、Optional や Lambda の導入などが絡んでくるなど、かなりチャレンジです。

開発初期段階であれば、いっせーのでできるかもですが、開発終盤では厳しいでしょう。 運用中のものであれば移行祭りが必要です。なので、ここまでできていれば十分ではあるでしょう。

ちょっとずつ移行

徐々に移行していきたいのであれば、Lambdaのコールバックのメソッドは生成しておいて、 新しいプログラムはオーバーロードのLambda方式のメソッドを使って、既存のコードは修正が入ったときにつどつどリファクタリングするというやり方もできます。

ただ、補完すると selectList() や selectEntity() が全部オーバーロードで二つに分身するので、ちょっとうっとおしいかもしれません。 やるなら本当に徐々に直していって、最終的には全部 Lambda に直す意気込みが必要でしょう。

ConditionBean の new 方式 (isMakeDirectConditionBeanSetup) をテーブル単位で互換させるオプションが用意されています。 これを使って少しずつ互換テーブルを消していくとよいでしょう。

e.g. テーブル単位でCBを互換モードにする @littleAdjustmentMap.dfprop
    ; isMakeDirectConditionBeanSetup = true
    ; makeDirectConditionBeanSetupTableList = list:{
        ; prefix:MEMBER ; PURCHASE ; prefix:Vendor ; prefix:White
    }

それでもコンパイルエラー検知ではあるので...

Optionalの方は、こういった器用なことができないので、移行するときや "えいやっ" とみんなで一斉に直すしかないでしょう。ただ、Lambdaもそうですが、コンパイルエラーの検知のできるものではありますので、"すごい頑張ればできる" という感じでしょうか。(でも、リフレクション詰め替えを使っていたらアウト)

Optionalにすると実装の安全性は格段に上がります。Lambdaは別に...明確なメリットが得られるかというとそうではないかもしれませんが、 実は Optional は多少苦労しても一番移行したいと思えるものかもしれません。

java.util.Date を LocalDate への移行は、さらにしんどいと思うので無理することはないでしょう。