データ登録

処理概要

DBFluteプロパティ databaseInfoMap.dfprop に定義されたスキーマに接続し、仕様に合致するファイルに定義されているデータを登録します。データの形式は三つで、以下の順番で処理されます:

  1. エクセルデータの登録 (../firstxls/..) @since 0.9.8.3
  2. エクセルデータの登録 (../reversexls/..) @since 1.0.4B
  3. TSVデータの登録 (../reversetsv/..) @since 1.0.4B
  4. TSVデータの登録 (../tsv/..)
  5. CSVデータの登録 (../csv/..)
  6. エクセルデータの登録 (../xls/..)

基本的には、デリミタデータ(TSV, CSV), エクセルデータ(/xls/) の順番と考えてください。 (DBFluteでは、TSVやCSVのような形式のデータを一律 デリミタデータ を呼びます)

firstxls や reversexls, reversetsv は特別な場合のみ利用される領域です。 もし、TSV, CSVよりも先に登録したいエクセルがある場合にだけ firstxls のエクセルデータを配置するというニュアンスです。また、reversexls, reversetsv は、LoadDataReverse による playsql への直接出力で利用されます。

登録データの順序

"common" の全てのファイルが登録された後に、"[repsEnvType]" のデータが登録されます。

  1. "common" 配下の (TSV --> CSV --> エクセル)
  2. "[repsEnvType]" 配下の (TSV --> CSV --> エクセル)

エラーハンドリング

一つでもデータ登録が実行した時点で、処理(ReplaceSchemaタスク)は中断します。

トランザクション

これらの処理は全て "オートコミットモード" で実行されます。

IDは直接指定で登録

"シーケンス" や "Identity"は利用せず、データファイル上のIDの直接指定で登録します。

シーケンスに関しては "データ登録後のシーケンスの現在値の調整" 機能の利用をお奨めします。

IDの直接指定での登録方法が "完全に" 許されていないIdentityを利用されているテーブルのデータは登録できません。 直接指定ができるIdentityの利用をお奨めします(例えば、DB2の "by default")。例えば、SQLServerのようにコマンドを実行することで直接指定が許されるような場合は、 ReplaceSchemaが内部的にそのコマンドを実行するので特に意識する必要はありません。

データファイルの配置仕様の概要

DBFluteクライアントの playsql ディレクトリ配下に配置します。

e.g. データファイルの配置仕様の概要(repsEnvTypeはデフォルトの "ut") @playsql
playsql
 |-data
    |-common // repsEnvTypeに関わらず常に登録対象となる(最初に登録される)
       |-tsv // 一番最初に登録される(存在しない場合はスキップ)
       |  |-[encoding]
       |     |-10-[table-name].tsv 
       |     |-20-[table-name].tsv
       |     |-defaultValueMap.dataprop   // デフォルト値プロパティ(非必須)
       |     |-convertValueMap.dataprop   // コンバートプロパティ(非必須)
       |     |-loadingControlMap.dataprop   // データ登録制御プロパティ(非必須)
       |-csv // TSVの次に登録される(存在しない場合はスキップ)
       |  |-[encoding]
       |     |-10-[table-name].csv
       |     |-20-[table-name].csv
       |     |-defaultValueMap.dataprop   // デフォルト値プロパティ(非必須)
       |     |-convertValueMap.dataprop   // コンバートプロパティ(非必須)
       |     |-loadingControlMap.dataprop   // データ登録制御プロパティ(非必須)
       |-xls // CSVの次に登録される(存在しない場合はスキップ)
       |  |-10-[title].xls
       |  |-20-[title].xls
       |  |-defaultValueMap.dataprop      // デフォルト値プロパティ(非必須)
       |  |-tableNameMap.dataprop         // テーブル名文字数制限回避プロパティ(非必須)
       |  |-notTrimColumnMap.dataprop     // トリムしないカラム指定プロパティ(非必須)
       |  |-emptyStringColumnMap.dataprop // 空文字許すカラム指定プロパティ(非必須)
       |  |-loadingControlMap.dataprop     // データ登録制御プロパティ(非必須)
    |-ut // repsEnvType が "ut" の場合に登録対象となる(commonの後に登録される)
       |-...(same structure as common directory)

replaceSchemaDefinitionMap.dfprop の applicationPlaySqlDirectory を使って、playsql ディレクトリを、DBFluteクライアント以外の場所にも(追加的に)作成することができます。@since 0.9.7.0

TSVデータの登録

タブ区切りのテキストデータを登録します。

データ編集が(エクセルに比べて)しづらい形式のため、エクセルで扱えない件数のレコードを登録する場合などに利用されます。

CSVデータの登録

カンマ区切りのテキストデータを登録します。

ディレクトリ名やファイル拡張子などの "tsv" が "csv" に変わり、データのセパレータがカンマに変わる以外は "TSVデータの登録" と全く同じ仕様となります。

エクセルデータの登録

エクセルで管理されたデータを登録します。

データ編集がしやすい形式のため、データ登録のメインの方法と言えます。 但し、扱えるレコード数はエクセルの上限に依存します。

環境ごとの切り替え

それぞれのデータ形式ごとの "common" および "[repsEnvType]" ディレクトリに配置データが、実行時の "repsEnvType" に合わせて登録されます。"common" は常に登録されますが、"[repsEnvType]" ディレクトリ配下のデータは実行時の "repsEnvType" と一致するディレクトリ配下のデータのみ登録されます。

"repsEnvType" は、"replaceSchemaDefinitionMap.dfprop" の "repsEnvType" にて定義されます。デフォルトは "ut" です。環境ごとに "repsEnvType" を切り替える場合は、DBFluteプロパティの環境ごとに切り替え機能を利用して切り替えます。

e.g. repsEnvType を "it" に設定 @replaceSchemaDefinitionMap.dfprop
repsEnvType = it
設定ファイル
replaceSchemaDefinitionMap.dfprop
プロパティ
repsEnvType (default: ut)
補足
  • 全てのデータファイル形式で有効

値のコンバート設定(dataprop)

データファイルに定義されている値をそのまま登録するのではなく、ある値にマッピングしたり変換をかけたりすることが可能です。 もともとはTSVやCSVでの利用を想定していた機能でしたが、エクセルデータでも利用することができます(@since 0.9.9.0A)

デフォルト値の設定(dataprop)

例えば、共通カラムのようなカラムの場合、ReplaceSchemaで登録するときは固定の値を登録しても特に問題が無い場合があります。 そのようなときにデータファイルにそれらカラムのデータを律儀に登録するのではなく、"あるカラムは一律のある値を登録する" ということが可能です。

データ登録制御(dataprop)

データ登録の処理を制御(微調整)するプロパティがあります。データファイルと同じディレクトリに loadingControlMap.dataprop という名前のテキストファイルを配置して、 それぞれのプロパティを定義します。@since 0.9.8.4

loggingInsertType

登録データのログ出力のタイプを指定することができます。

isSuppressBatchUpdate

バッチ更新をせず都度登録をするか否かを指定することができます。

isSuppressColumnDefCheck

データファイルのカラム定義の、DB上存在チェックを抑制するか否かを指定することができます。

dateAdjustmentMap

エクセルやTSV上の日付データに対する相対的な日付調整を指定することができます。

largeTextFileMap

外部のテキストファイルから文字列を取得して、データを登録することができます。

シーケンス値の調整

データ登録後は、シーケンスの現在値と登録されているデータのIDの最大値が大きくズレてしまいます。例えば、会員IDが既にテストデータとして "20" まで登録されているのに、シーケンスは再作成されたばかりで "1" が取得されてしまうと、ReplaceSchema実行後、アプリケーションのテスト実行などの(シーケンスを利用した)登録処理でエラーになってしまいます。

"replaceSchemaDefinitionMap.dfprop" の "isIncrementSequenceToDataMax" を "true" に設定することで、"sequenceDefinitionMap.dfprop" にて定義されているシーケンスの値が、全てのデータファイル形式のデータ登録後に調整されます。(次のシーケンスの採番処理で、登録されたデータのIDの "最大値+1" の値が取得されるようになります)

設定ファイル
replaceSchemaDefinitionMap.dfprop
プロパティ
isIncrementSequenceToDataMax (true or false)
補足
  • シーケンスの増分値が "1" であることを想定
  • シーケンスがサポートされているDBMSのみ有効

内部的には、実際にシーケンスで採番して取得された値と登録されたデータのIDを比較して、適した値になるまで採番し続けることで実現しています。 登録されたデータが一件も存在しない場合は、そのテーブルのシーケンスに対して特に何もしません。 また、実際に調整処理が実行されるのは "最終チェック" の全てのSQLが終了した後です。

シーケンスの増分値が "2" 以上なら?

基本的には "シーケンスの増分値" は "1" であることを想定していますが、"2" 以上の場合でもプロジェクトとして許容できるのであれば特に問題なく利用可能です。

データのIDの増分量がシーケンスと同じ

シーケンスの増分値
10
データのIDの最大値
30 (10, 20, 30)
調整後の "nextval"
40

データのIDの増分量がシーケンスと違う

シーケンスの増分値
10
データのIDの最大値
23 (1, 3, 5, ..., 21, 23)
調整後の "nextval"
40

"登録データのIDの増分量" と "シーケンスの増分値" は同じにするのがお奨めです。

バイナリデータの登録

画像ファイルなどバイナリデータも登録できます。(@since 0.9.8.2)

例えば、エクセルデータの登録で画像ファイルを扱うと場合は、エクセル上ではそのエクセルファイルから画像ファイルへの相対パスを記載しておくことで、 データ登録時にその画像ファイルが読み込まれてテーブルに登録されます。TSVやCSVでも同様です。

e.g. テストデータとして画像ファイルを登録 @playsql
playsql
 |-data
    |-ut
       |-xls
       |  |-images
       |  |  |-logo.png
       |  |-10-foo.xls // images/logo.png と記載

パスはスラッシュ区切り "/" で記述します。パスの先頭にスラッシュを付けると絶対パスになります。

該当カラムがバイト配列にマッピングされるデータ型で、かつ、指定されたデータが文字列の場合において、バイナリデータとしての登録だと判断されます。 参照先にファイルがない場合は例外となります。

ラージテキストの登録 (外部ファイル)

ラージサイズのテキストを登録するのに、外部のテキストファイルから文字列を取得して登録することができます。

loadingControlMap.dataprop の largeTextFileMap にて、対象となるカラムを指定する。

e.g. largeTextFileMapを設定 @loadingControlMap.dataprop
    ; largeTextFileMap = map:{
        MEMBER = list:{ MEMBER_LARGE_NAME }
    }

TSV や XLS では、スラッシュ区切りの相対パスで外部ファイルを指定します。 すると、そのファイルの内容を読み込んでデータとして登録するようになります。 (絶対パスも使えますが、あまり使いどころがないかも...)

FreeGenなど、他の都合で外部ファイルになっていた方が良い場合にも活用できます。 (LastaFluteだと、メールテンプレートをDB管理する時など)

登録エラーが発生したら?

データ登録は ReplaceSchema の中で最もエラーが発生しやすい場所です。例えば、NotNull制約のカラムに "null" が定義されていたり、数値のカラムに文字列が定義されていたりと、データ不備はすぐにエラーとして検知されます。

とにかくログを

エラーが発生したら真っ先に コンソールログ もしくは [DBFluteクライアント]/log/dbflute.log を確認しましょう。例外メッセージを必ず読み、そして、それまでの登録されたデータのログから "どのデータでエラーが発生したのか" を特定します。

バッチ更新なしの自動リトライ

データは(プロパティで変更しない限り)バッチ更新されます。スピードは確保できますが、その代わりエラーが発生時に "どのレコードでエラーが発生したのか" が非常にわかりにくくなってしまいます。

そこで ReplaceSchema では登録エラーが発生したときに、バッチ更新をせずそのテーブルのデータ登録のリトライを行い、 エラー発生レコードの補足情報を例外メッセージに付与します。@since 0.9.8.4

ただし、これはエクセルデータの登録に限ります。(デリミタデータでは未サポート)

e.g. エラー発生時に "バッチ更新なしの自動リトライ" したログ {MySQL} @Console
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Failed to register the table data.

[Xls File]
playsql/data/ut/xls/30-product.xls

[Table]
PURCHASE

[SQLException]
java.sql.BatchUpdateException
Column 'PURCHASE_DATETIME' cannot be null

[Non-Batch Retry]
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException
Column 'PURCHASE_DATETIME' cannot be null
[PURCHASE_ID, MEMBER_ID, PRODUCT_ID, PURCHASE_DATETIME, PURCHASE_COUNT, PURCHASE_PRICE, PAYMENT_COMPLETE_FLG, REGISTER_DATETIME, REGISTER_USER, UPDATE_DATETIME, UPDATE_USER, VERSION_NO]
{41, 4, 11, null, 4, 1700, 1, 2011-05-28 16:51:52.991, foo, 2011-05-28 16:51:52.991, foo, 0}
Row Number: 41

[Bind Type]
PURCHASE_ID = java.lang.Long
MEMBER_ID = java.lang.Integer
PRODUCT_ID = java.lang.Integer (NumberStringProcessor)
PURCHASE_DATETIME = java.sql.Timestamp (DateStringProcessor)
PURCHASE_COUNT = java.lang.Integer (NumberStringProcessor)
PURCHASE_PRICE = java.lang.Integer (NumberStringProcessor)
PAYMENT_COMPLETE_FLG = java.lang.Integer (NumberStringProcessor)
REGISTER_DATETIME = java.sql.Timestamp
REGISTER_USER = java.lang.String (RealStringProcessor)
UPDATE_DATETIME = java.sql.Timestamp
UPDATE_USER = java.lang.String (RealStringProcessor)
VERSION_NO = java.lang.Long (NumberStringProcessor)
* * * * * * * * * */

ただし、厳密にリトライで同じエラーが発生するかどうかは確実とは言い切れませんので、 メイン処理のエラー内容とリトライのエラー内容とを見比べて同じエラーかどうか(リトライが成功しているかどうか)を確認してから参考にするようにしてください。

バッチ更新せずデータ登録

万が一のために、プロパティで "バッチ更新をしないで登録" するようにすることができます。 登録データが全てログに出力されているようになっていれば(デフォルトでは出力される)、エラー発生レコードを特定できます。

最低でも一件はテストデータを

テストデータが全く入ってないテーブルがあると非常に開発しづらいものです。

emptyTableMapでチェック

ReplaceSchemaの最後 (TakeFinally) の段階で、"テストデータが一件も存在しないテーブルがあったらエラー" というアサートが可能です。replaceSchemaMap.dfprop の conventionalTakeAssertMap の emptyTableMap を使います。 @since 1.1.7

e.g. テストデータが一件も存在しないテーブルがあったらエラー @replaceSchemaMap.dfprop
    # /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # o conventionalTakeAssertMap: (NotRequired - Default map:{})
    #  Does it assert data by conventional rule in take-finally
    #  For example, you can check empty table (no-data).
    #
    ; conventionalTakeAssertMap = map:{
        ; emptyTableMap = map:{
            ; isFailure = true
            ; workableRepsEnvTypeList = list:{ut}
            ; tableExceptList = list:{}
            ; tableTargetList = list:{}
            ; errorIfFirstDateAfter = null
        }
    }
    # - - - - - - - - - -/

これから追加されるテーブルだけチェック

既存テーブルは諦めるとして、"これから追加されるテーブルだけはテストデータを一件以上入れよう" という場合は、errorIfFirstDateAfter に "今日" を入れましょう。

e.g. 新しいテーブルで、テストデータが一件も存在しなかったらエラー @replaceSchemaMap.dfprop
            ...
            ; tableTargetList = list:{}
            ; errorIfFirstDateAfter = 2018/09/28
        }
        ...

比較対象となるテーブル作成日(firstDate)は、SchemaPolicyCheck における firstDate と仕組みは同じです。