当たり前のことを書きます。
結論
制約はDBが付与するデフォルト名ではなく、しっかり名前を付けましょう。
デフォルト名ではない場合何が起きたのか
環境
- Java
- DB
- H2(ユニットテスト)
- PostgreSQL(結合テスト・本番環境)
- DBマイグレーション
- flyway
何が起きたのか
後から制約を変更したくなった時に、変更するために必要な制約名が分かりづらいです。
私の環境では、ユニットテストはH2を使用し、結合試験以降はPostgreSQLを使用しています。
デフォルトでの命名規則はDBによって異なるので、H2とPostgreSQLは違う名前が生成されます。
なので、制約を変更したことを確認するためのテストを書くためには、H2とPostgreSQLの両方の制約を再生成するためのSQLを実行してあることが大前提になってしまいます。
PostgreSQLはGUIで制約の名前を確認できるのですが、H2だとインメモリなのでテスト中でないと確認できません。
なので、Mapperのユニットテストで下記のSQLを実行し、その結果を元に制約名を調べていました。
-- 制約名を確認するコード SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS ORDER BY TABLE_NAME, CONSTRAINT_NAME;
もし、最初から意図的に命名を行っていれば、わざわざこんな苦労をせずとも再生成ができます。
後々苦労したくなければ命名することをオススメします。
flywayの場合、実行順番が決まっているので確実に同じ制約名が振られることは理解していましたが、H2の制約名は連番なので1個でもズレると大変な事故が発生します。
これを担当してリリースしたときは、生きた心地はしませんでした…。
制約名をつける方法
TABLE生成時に制約名をつける方法
CREATE TABLE テーブル名( カラム, CONSTRAINT 制約名 PRIMARY KEY() )
後から制約名を付ける方法
ALTER TABLE テーブル名 ADD CONSTRAINT 制約名 FOREIGN KEY (別テーブル) REFERENCES カラム (別テーブル);
具体的になんの制約を変更したかったのか
データ構造
- 注文
- 明細
- その他もろもろ
- 明細
ユースケース
参照性制約をUPDATE RESTRICT DELETE RESTRICTにしていました。
キー名の変更はできないし、削除もできない設定ですね。
なので、注文データを削除する処理は制約上、以下の順で処理をしなければなりません。
- 孫のその他テーブルを削除
- 子の明細テーブルを削除
- 注文テーブルを削除
注文テーブルに合計で20テーブルくらいくっついてました。
処理としてできますが、なぜDBの構造を意識しなければいけないのでしょうか。
これはドメインの流出ではないでしょうか。
そういうことを意識せずに注文テーブルを削除したいのです。
なので参照性制約をUPDATE RESTRICT DELETE CASCADEに変更しました。
これで、注文テーブルを削除するだけで、他の紐づくテーブルは一気に削除できるようになりました。
DB側に任せた方が、適切な内部処理をしてくれそうだから、処理の高速化になります。
このテストを書こうとしたときに、H2での参照性制約違反エラーが出て苦労した記憶があったので、今回の記事を備忘録にしています。
ローカルのPostgreSQLで動作検証した後に、テストで動かなかったので非常に混乱しました…。
そもそもH2使わなきゃいいんじゃない
わかる。
わかります。
他にも、H2とPostgreSQLの方言が合わなくてうまくいかなかったケースは大量にあります。
ただ、H2ってインメモリで展開できるDBなので、並列でテストできて高速なのが非常に高いメリットです。
PostgreSQLでは30分くらいかかるテストでも、4並列で起動できるので10分で完了しました。
テストって1日に何度も実行するものなので、時間が短縮されるだけで開発効率が非常に高くなります。
総合的な開発効率を優先するか、開発者としての方言が無いという安全性を優先するか。
要はバランスですが、どっちも優先したい気持ちがあるので、難しいです。
終わりに
テーブル名やカラム名は使っても、制約名は基本的に使わないのでデフォルト名でいい気はしています。
基本的に、DBを全環境で同じものを使用すれば問題は発生しませんし。
ただ、上手くいかなかった時のリカバリに時間がかかってしまうので、最初から設計できるのであれば設計したほうがいいです。
当たり前ですが、デフォルトで良いものと、デフォルトではいけない部分はちゃんと理解して使ったほうがいいですね。