DBFluteのノリ切れないやつ、バッチ登録

複数のレコードを一気に登録する機能です。
ループで insert() をぐるぐるするよりも、
通信量が(たぶん)少ないので速い。
プログラムでfor文を書かなくていい。
ログもすっきり見やすくなる。

Behavior - batchInsert() | DBFlute

なんか便利かって言うと...まあそれなりに便利。
でも超便利かって言うと...そこまででもない。
batchInsert()は、ちょっと注意点があります。
デフォルト制約が利用できません。
全てのカラムがinsert文に列挙されるので、
null の値は null として登録されます。

そもそもjfluteとしてはDB設計的に
「デフォルト制約は極力使って欲しくない」
(何をデフォルトにするかも業務ロジックの一つであり、
それが制約に入ると見えづらくなってしまうので)
という思いがありますが、
とあるパターンでは利用を認めざるを得ない
場面があります。運用後のDB変更ですね。

運用後にNotNull制約のカラムを追加するとき、
通常の insert() であれば、
放っておいてもデフォルト制約が適用されますが、
batchInsert() はNotNull制約で落ちます。
厳密には、Entityを再自動生成すると落ちます。
(再自動生成しなければ、insert文には
そのカラムが列挙されないのでデフォルト制約が有効)

直せばいいじゃん!

That's right!
But 既にどこで何を insert してるのか、
把握できてないシステムがあるのも事実。
それはそれで問題だけど、事実は事実。

Eclipseのショートカット
「ctrl + shift + G」
で探せばいいじゃん!

That's right!
なので、カラム追加時は、そのテーブルのBehaviorの
batchInsert()のメソッドの呼び出しを確認すると
良いでしょう。良いでしょうというか確認お願いします。

ただ、一つのDBにアプリが二つ以上あって、
片方のアプリ(A)のためのカラム追加で、
こちらはアプリも修正してテストもしてリリース。
でも、もう片方のアプリ(B)でもbatchInsert()してて、
こっちは今回の修正でリリースはしたくない。
この場合、Bには再自動生成が反映されないので、
Bの batchInsert() はデフォルト制約で動き続けますが、
ここでちょっと怖いのは、
いざBの方が別の修正でDBの再自動生成が反映されるとき、
その batchInsert() 部分が落ちる対応を忘れないか...

ちょっと特殊なケースかもしれませんが、
運用し始めたら、あまり不要なリリースは認められないもの。
システムがちょっとでも止まってしまうわけですから、
アプリAをリリースするのになぜアプリBもリリースするの?
って話になってしまいます。こういうジレンマもあります。
また、DB設計者の中には、
「えっ、そんなんinsert文の書き方おかしいでしょ!?」
っていう反応があったりします。
アプリのinsert箇所のチェックなしでカラム追加
なんてできるでしょ!? なんでできないの!?
アプリの作りおかしくない!? DBFluteおかしくない!?
っていう話になることが。

batchInsert()も、必ずカラムを一個一個
指定するやり方にした方がよかったでしょうか!?
それはそれでとても面倒なインターフェースかもしれません。
更新ならカラム数も限られるのでその方が良いかもですが、
登録の場合はそのテーブルのほぼ全てのカラムを列挙します。

また、再自動生成されるとinsertで列挙されるカラムが
自動的に増えることにもメリットはあるはずなので、
ここは仮に過去に戻ったとしても悩ましいところです。

そしてそもそも、batchInsert()がどれだけ速いのか!?
ループで insert() をぐるぐる回すのと比べて、
どれだけ速いのか!? 件数が多ければ多いほどってのは
当然でしょうが、3件とか10件とかだとほぼ差はないかと...
1万件とか10万件とかでやっと効果が出てきやすいもの。
でも、複数件の登録だから batchInsert() っていう
利用も多いはずなので、もうこのメソッドは無くせない。
それだけのきっかけでデフォルト制約がどうのこうの
っていう問題が発生してしまうのはなんとも歯がゆい。

しかもしかもしかも、MySQLなんかは実際には
バッチ更新ってサポートしていないので、
JDBCドライバの中で単にぐるぐるしてるだけで、
スピード的なメリットはあんまりな...い...
このデフォルト制約の問題が、もっと発生頻度の高い
深刻な問題であれば、思い切って「えいやっ」で変更、
というきっかけにもなりますが、
なんとも地味なので(その場になれば重要だけど)。
でも一方で、バッチ登録のメリットもそこまでではない。

過去には戻れないので、追加的な対応しかできません。
第二引数でカラムを指定する batchInsert() を
新たにオーバーロードで用意するってのが思い付きますが、
あんまり使われなさそうだなぁと。
Eclipseと連携して、カラム列挙を補完してくれるとか
じゃないとなんとも説得力のあるメソッドにはならないと。

「デフォルト制約を使ったカラム追加したいなら、
そっちのメソッドを使ってね」っていう風にしたとしても、
実際に使う人がそこを意識して判断するか!?
ってあんまりしないかなぁ。現実的なI/Fじゃない。
それじゃぁ単なるフレームワークの責任逃れになっちゃう。

引数で渡されたリストの中を全部見て、
全てのレコードでnullのカラムは列挙しないとか!?
いやいや、余計に問題が見えづらくなってしまう。
たまたま全部nullってだけで、nullじゃないときは落ちる。

DBFluteでデフォルト値をメタデータから取得して、
DBFluteの中で設定してあげる!?
いやいや、メタデータのデフォルト値がこれまた
信用できなくて、ちゃんと思った通りの動作するか不安。

SQLの文法で、NotNull制約 + デフォルト制約のとき、
「値があればそれが登録されて、nullならデフォルト値」
っていう書き方ができればいいのにー。
(カラムを列挙するとデフォルト制約効かないんだもん)
今言えることは、やっぱり
batchInsert() を「ctrl + shift + G」すれば、
影響範囲わかるからそれで確認してねー、っとこ。
念のため、varyingBatchInsert() や
lumpCreate() も。(使ってる人いないだろうけど)

最初、そこまで深く考えられず、
バッチ登録を装備してしまったことは反省。
だからといって、深く考えて答えが出たかというと、
それもわからない。でも、何かしらフィット感のある
解決法は考えられたかもしれないけど後の祭り。