ローカルで動かすときに、起動したらもうテストデータが入っていてほしい。そう思って 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.sqldata.sql で、置き場所は src/main/resources 直下です。まずはここから外れていないか確認しましょう。

DBプラットフォームごとにスクリプトを切り替えたいときは、data-${platform}.sql という命名が使えます。spring.sql.init.platform で指定した値が ${platform} に入ります。

spring.sql.init.platform=postgresql

この設定なら schema-postgresql.sqldata-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-autoschema.sql の両方でテーブルを定義してしまうパターンです。

ddl-auto=createupdate はHibernateがエンティティからDDLを生成します。そこに schema.sql で同じテーブルの CREATE TABLE を書くと、定義が二重になって競合したり、片方が上書きしたりと、予期しない挙動になりがちです。

避け方はシンプルで、スキーマ定義の責務をどちらか一方に寄せる ことです。

  • schema.sql でスキーマを管理するなら、ddl-auto=none にしてHibernateの自動生成を止める
  • Hibernateにテーブル生成を任せるなら、schema.sql は使わず data.sqldefer-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が実行されない」チェックリスト

つまずいたら、上から順に確認してみてください。

  1. 外部DBなのに spring.sql.init.mode=always を設定していない … デフォルトの embedded では外部DBで動きません。ここが最頻出です。
  2. spring.jpa.defer-datasource-initialization=true を入れ忘れている … Hibernateより先に流れて「テーブルが無い」で失敗します。
  3. ファイル名・置き場所が違うdata.sqlsrc/main/resources 直下にあるか、綴りは合っているか。
  4. SQL側の文法エラー … セミコロン漏れ、DBの方言違いなど。ログにエラーが出ていないか確認します。
  5. 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-autoschema.sql のどちらかに決める。この3点でつまずきの大半は解消します。

そのうえで、本番のスキーマ変更管理まで抱え込ませないこと。開発のシードは標準のSQL初期化、本番のマイグレーションはFlyway/Liquibase、と役割を分けておけば、プロジェクトが育っても破綻しません。