ローカルで動かすときに、起動したらもうテストデータが入っていてほしい。そう思って data.sql を置いたのに、なぜか実行されない。あるいは「テーブルが存在しません」というエラーで起動すら失敗する。Spring Bootを触り始めた頃に、ほぼ全員が一度は踏むつまずきですよね。
この記事では schema.sql / data.sql の配置と設定、そしてHibernate(JPA)との実行順序の落とし穴を整理して、「data.sqlが実行されない」を自分で診断できるところまで持っていきます。あわせて、本番のスキーマ管理はFlyway/Liquibaseに任せる、という使い分けの基準も示します。
schema.sqlとdata.sqlの役割
この2つは名前のとおり役割が分かれています。
schema.sql… DDL。テーブルやインデックスの定義を書くdata.sql… DML。INSERTなどで初期データを投入する
どちらも src/main/resources の直下(クラスパス直下)に置いておくと、Spring Bootが起動時に自動で見つけて実行してくれます。特別な設定なしで動くのが手軽なところです。
-- src/main/resources/schema.sql
CREATE TABLE IF NOT EXISTS book (
id BIGINT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author VARCHAR(100)
);
-- src/main/resources/data.sql
INSERT INTO book (id, title, author) VALUES (1, '吾輩は猫である', '夏目漱石');
INSERT INTO book (id, title, author) VALUES (2, '走れメロス', '太宰治');
最初に押さえておきたいのは、これはあくまで 開発・テスト用のシード投入向け の機構だという点です。バージョン管理も差分適用もないので、本番のスキーマ変更管理には向きません。そこは後半で改めて触れます。
配置場所と命名規約
デフォルトのファイル名は schema.sql と data.sql で、置き場所は src/main/resources 直下です。まずはここから外れていないか確認しましょう。
DBプラットフォームごとにスクリプトを切り替えたいときは、data-${platform}.sql という命名が使えます。spring.sql.init.platform で指定した値が ${platform} に入ります。
spring.sql.init.platform=postgresql
この設定なら schema-postgresql.sql と data-postgresql.sql が読まれます。H2用とPostgreSQL用でDDLの方言が違う、といった場面で便利ですね。
置き場所をデフォルトから変えたい場合は、パスを明示的に指定します。
spring.sql.init.schema-locations=classpath:db/schema.sql
spring.sql.init.data-locations=classpath:db/data.sql
spring.sql.init.modeの意味
「data.sqlが実行されない」の原因として一番多いのが、この spring.sql.init.mode です。値は3つあります。
embedded… H2やHSQLDBなど組み込みDBのときだけ実行(デフォルト)always… DBの種類を問わず常に実行never… 初期化を一切行わない
ポイントはデフォルトが embedded だということ。つまり、MySQLやPostgreSQLなど外部DBに接続していると、何も指定しなければスクリプトは実行されません。ローカルのH2では動いていたのに、DBを差し替えた途端に初期データが入らなくなった、というのはこれが原因です。
外部DBでも実行したいなら、明示的に always を指定します。
spring.sql.init.mode=always
逆に、途中から初期化を止めたいときは never にすればまるごと無効化できます。
Hibernateとの実行順序に注意
JPA(Hibernate)を使っている場合、もう一つ大きな落とし穴があります。それが実行順序です。
Spring Boot 2.5以降、SQLスクリプトの初期化はHibernateによるテーブル生成(ddl-auto)よりも 先に 走るようになりました。素直に考えると、Hibernateがテーブルを作る前に data.sql が流れてしまうわけです。すると当然、
org.springframework.jdbc.BadSqlGrammarException: ... Table "BOOK" not found
のように「テーブルが存在しない」エラーで落ちます。エンティティ定義はあるのにテーブルが無い、というのはこの順序が噛み合っていないサインです。
Hibernateが作ったテーブルに対して data.sql を流したいなら、初期化をHibernateの後ろにずらす設定を入れます。
spring.jpa.defer-datasource-initialization=true
この1行を入れ忘れているだけ、というケースが本当に多いです。JPAのエンティティから自動生成したテーブルにシードを入れたいときは、ほぼ必須と思っておくとよいでしょう。JPAエンティティの定義そのものについては JPAエンティティのリレーションマッピング の記事もあわせてどうぞ。
ddl-autoとschema.sqlの併用で起きる競合
もう一つありがちなのが、ddl-auto と schema.sql の両方でテーブルを定義してしまうパターンです。
ddl-auto=create や update はHibernateがエンティティからDDLを生成します。そこに schema.sql で同じテーブルの CREATE TABLE を書くと、定義が二重になって競合したり、片方が上書きしたりと、予期しない挙動になりがちです。
避け方はシンプルで、スキーマ定義の責務をどちらか一方に寄せる ことです。
schema.sqlでスキーマを管理するなら、ddl-auto=noneにしてHibernateの自動生成を止める- Hibernateにテーブル生成を任せるなら、
schema.sqlは使わずdata.sql+defer-datasource-initialization=trueに寄せる
外部DBで schema.sql を使う構成なら、YAMLではこう書けます。
spring:
jpa:
hibernate:
ddl-auto: none
sql:
init:
mode: always
schema-locations: classpath:schema.sql
data-locations: classpath:data.sql
どちらがスキーマの持ち主なのかを最初に決めておくと、この手の混乱はほとんど起きなくなります。
「data.sqlが実行されない」チェックリスト
つまずいたら、上から順に確認してみてください。
- 外部DBなのに
spring.sql.init.mode=alwaysを設定していない … デフォルトのembeddedでは外部DBで動きません。ここが最頻出です。 spring.jpa.defer-datasource-initialization=trueを入れ忘れている … Hibernateより先に流れて「テーブルが無い」で失敗します。- ファイル名・置き場所が違う …
data.sqlがsrc/main/resources直下にあるか、綴りは合っているか。 - SQL側の文法エラー … セミコロン漏れ、DBの方言違いなど。ログにエラーが出ていないか確認します。
ddl-autoとの二重定義になっていない … 前節の競合パターンです。
だいたいこの5点のどれかに当てはまります。ログをよく読めば、テーブル未作成なのか文法エラーなのか、どのフェーズで失敗しているかは見えてきます。
Flyway/Liquibaseとの使い分け
ここまでの schema.sql / data.sql は手軽な反面、大きな弱点があります。バージョン管理も差分適用もロールバックも持たない、という点です。毎回まるごと流し直す前提なので、本番で少しずつスキーマを変えていく運用には向きません。
そこは素直にマイグレーションツールに任せましょう。用途で切り分けると、こう整理できます。
| 用途 | 推奨手段 |
|---|---|
| ローカル開発・テストのシード投入 | schema.sql / data.sql |
| 本番のスキーマ変更履歴管理 | Flyway または Liquibase |
| SQLで直感的に書きたい | Flyway |
| DB非依存・XML/YAMLで管理したい | Liquibase |
ざっくり言えば、捨てても困らない開発用データは標準のSQL初期化、育て続ける本番スキーマはFlyway/Liquibase です。それぞれの具体的な書き方は Flywayでデータベースマイグレーション と Liquibaseでデータベースマイグレーション にまとめてあるので、本番向けを検討するならそちらを読んでみてください。
なお、テスト用のデータ準備という文脈では @DataJpaTestでリポジトリのスライステスト や、環境ごとの設定切り替えを扱う Spring Bootのプロファイルで環境別設定を安全に切り替える も相性がいいです。
まとめ
schema.sql / data.sql は、押さえるべきポイントさえ分かれば怖くありません。外部DBなら mode=always、Hibernateと併用するなら defer-datasource-initialization=true、そしてスキーマの持ち主を ddl-auto と schema.sql のどちらかに決める。この3点でつまずきの大半は解消します。
そのうえで、本番のスキーマ変更管理まで抱え込ませないこと。開発のシードは標準のSQL初期化、本番のマイグレーションはFlyway/Liquibase、と役割を分けておけば、プロジェクトが育っても破綻しません。