冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

【追記修正】Spring Boot JPAでTableアノテーションを使ってテーブル名を指定する時の注意事項

既存のデータベース使う場合はNamingStrategyのデフォルトに注意、というお話。

こないだの続きで、別のSpring Bootを使って既存のデータベースのテーブルからデータを引っ張りだすAPIを作っていたら、ちょっとトラップにはまってしまったのでまとめます。

起きたこと

新しく参照するテーブルのエンティティを作って、データ初期投入のためのINSERT文も追加。h2を利用するプロファイルで実行したらデータの初期投入時にテーブルがない、と怒られる。ログを見る限り、DDLのエクスポートから実行までの部分にエラーは出ていませんでした。

ちなみに結果として重要なことなので追記しておきますが、テーブルはこんな感じの命名規約(私が決めた訳ではない)。

T_D_FooBar

頭にプリフィクスが二つついて、その後にテーブル名をキャメルケースで表現しています。プリフィクスとかエンティティクラスの名前で表現したくないので、プリフィクスを省略したテーブル名をクラス名にして、Tableアノテーションでテーブル名を指定していました。

デモ環境で動かしてみた

どこから手を付けていいかわからなかったので、動く状況を変えてエラーログの出方がどう変わるか見てみようということで、最初に初期値をロードしないデモ環境のプロファイルで動かしてみました。

アプリケーションの起動は問題なくできます。ところが、APIにアクセスしてみると以下のようなログが出てきました。

2015-07-29 16:28:01.560 ERROR 5717 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : オブジェクト名 't_d_foo_bar' が無効です。
2015-07-29 16:28:01.571 ERROR 5717 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause

com.microsoft.sqlserver.jdbc.SQLServerException: オブジェクト名 't_d_foo_bar' が無効です。

おや、 t_d_foo_bar ??テーブル名に指定したのは T_D_FooBar です。念のためにデータベースを見てみましたが、やはり T_D_FooBar で定義されています。

結論

Tableアノテーションのname属性はキャメルケースの場合にアンスコを入れてテーブル名を解釈し、クエリを生成するということでしょう。

Tableアノテーションの指定を T_D_Foobar に変更したら正しく挙動しました。

以下、追記]

あれから、ぜっちゃんとゆとり氏にhibernateのNamingStrategyの存在について教えてもらいました。

どうやらSpringBootにおけるhibernateのテーブル名のNamingStrategyがImprovedNamingStrategyになっているっぽいです。JavaDocを読む限り、キャメルケースだろうがスネークケースだろうが、単語の境をアンダースコアにする命名既約だと思われます。

Spring boot JPA annotation naming strategyvijay360.wordpress.com

d.hatena.ne.jp

これをEJB3NamingStrategyに変更すると、混在ケースでも正しく参照できました。 application.yml は以下のような感じで。

spring:
  profiles:
    active: dev
  jpa:
    hibernate:
      naming_strategy: org.hibernate.cfg.EJB3NamingStrategy

---
spring:
  profiles: dev
  datasource:
    url: jdbc:h2:mem:test;MODE=MSSQLServer;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password: 
    driverClassName: org.h2.Driver

---
spring:
  profiles: production
  datasource:
    url: jdbc:sqlserver://[本番環境のアクセス情報]
    username: [ユーザ名]
    password: com.microsoft.sqlserver.jdbc.SQLServer
    initialize: false

何が困るか

コンパイルエラーが出たり、アプリケーションのロードとかで失敗する訳じゃないので、なにがしかのクエリを実行しないことにはわからないというのが面倒ですね。それに、実行した所で開発環境のように単にテーブルが見つからない、としかわからないケースも。。。

実行したクエリのテーブル名が間違っている場合はエラーメッセージに出してくれるので、開発環境の場合は「間違ったテーブル名で作成されて、正しいテーブル名のクエリが通らない」状況で、結果エラーログに出てくるのは正しいテーブル名。デモ環境の場合は「正しい名前のテーブルは既にあって、間違ったテーブル名で生成したクエリが通らない」状況で、結果エラーログに出てくるのは間違ったテーブル名。ここの違いがなかったら正直よくわかりませんでした。

まとめ

現時点ではこの挙動が仕様なのかバグなのかはわかりません。

ただ、テーブルやカラム名はスネークケース+大文字で命名規約を統一するのが正解かと。命名規約がしっかりしていない既存のテーブルを扱う場合には頭の片隅に置いていた方が良いかもしれません。

これはトラップすぎる。

おまけ

他のメンバーが同じ所で引っかかってましたとさww