SpringBoot-JPA-Hibernateのハマりメモ
この記事について
この記事は自分がアプリケーションの開発中にハマった点と、発見できた場合は原因、解決方法をまとめたものです。
この記事で問題としているものの、そもそもの利用法が間違っているケースや、解決方法にもっと良いものがあるかもしれません。
環境
- Spring Boot 2.2.4
- Hibernate 5.4.10
- MySQL 8.0.16
遭遇した問題たち
PK以外をキーにした関連を作るとエラーが出る
PK以外をキーにした関連を作るとエラーが出る
データベース上では普通にあり得る話なのですが、Hibernateの実装上問題が発生することがあります。
ユースケース
@Entity
class Parent {
@Id
Long id;
@NatulalId
Integer key;
String firstName;
String lastName;
}
@Entity
class Child {
@Id
Long id;
@ManyToOne
@JoinColumn(name = "parent_key",referencedColumnName = "key")
Parent parent;
}
上記のような、Hibernate自身が推すPKをサロゲートキー、@NaturalId
で自然キーを指定するような利用法のとき、JPA仕様の範囲内だとkeyがUniqueを指定されており、idの代わりに外部キーとして機能させたい場合です。
自然キーで関連付けを行うとchild.getParent()などの実行時にキャスト絡みでエラーが発生します。1
原因
解決
参照されるエンティティ(上記だとParent)をSirializableにする。
これを鑑みるに、Hibernateで使用するエンティティはすべてSirializableである方が無難かもしれません。
issue trackerをざっと眺めたところ、PKではないUniqueカラムで関連付けするとこれ以外の不具合にも遭遇する可能性がある模様です。
2020/05/28追記 エンティティのSirializableについて
JPAの仕様上、エンティティがDBとだけやり取りされるときにSirializableは必要ありませんが、HTTPセッションにオブジェクトとして保存するなど、JavaEEの範囲で使用される場合にはエンティティのSirializableが必要になる、という事のようです。
思わぬところでエラーが出ることを考えると、Sirializableになっている方が望ましいようです。
FetchType.LazyのエンティティがJacksonによるシリアライズ時にエラーを出す
原因
Jacksonがシリアライズを行う時にアクセスしているLazy指定のプロパティの実体はHibernateのプロキシであるため
解決
Hibernateのバージョンに対応するJacksonのプラグインを追加する。
初期状態でSpringBootはHibernateとJacksonに依存しているのになぜこれは入っていないんだろう。2
Stack Overflow:No serializer found for... って?
Stack OverFlow:SpringのJacksonでLazy属性を抑制する設定
みんな苦労してます。
Spring JPAエンティティのJacksonシリアライズがLazyもトリガする問題の解決
上記が非常にわかりやすく説明と解決方法を示してくれます。
というわけで念のためにプラグインのGitHubです。内容はあまり親切じゃないです。
ついでに、上記GitHubで紹介されているJacksonのアノテーションによる出力制御解説(無限ループ回避策)
カラム名やテーブル名関連のエラーが出る(追記・修正2020-02-21)
これは仕様の話ですね。バグではありません。
原因
デフォルト命名戦略はJPAアノテーションよりも後に処理される
単に優先順位の問題で、JPA(≒Hibernate)のテーブル名やカラム名を指定するアノテーションが使用されている場合、フィールド名からのカラム名変換が処理されていないため、エラーになります。
仕様の理解が足りていませんでした。
Hibernateでは、2段階の変換を経てDBのカラム名が生成されます。
- エンティティ名、フィールド名から論理名を生成する
- 論理名を物理名に変換する
現在のSpringのデフォルトでは、
-
論理名の生成
- クラス名、フィールド名を論理名として取得する
-
@Column
や@Table
で指定されていれば、その文字列を論理名にする
-
物理名の生成
- ピリオドをアンダースコアに置き換える
- キャメルケースをスネークケースに置き換える
という設定になっています。
@Column
や@Table
で指定された文字列を論理名にするため、フィールドやクラス名をキャメルケースで指定しているなら、アノテーションで指定する名前もキャメルケースにしなくてはなりません。ここで物理名を(つまりスネークケース)を指定していると、"Table [table name] contains physical column name [column name] referred to by multiple logical column names:"(テーブルの物理カラム名に対応する論理名2個あるんですけど)とエラーが出ます。
解決
JPAのアノテーションでカラム名等を指定している場合、フィールドに3@Column
でnameを指定しておかなくてはなりません。思ったより手抜きができません。
仕様とエラーメッセージはきちんと読みましょう。
物理名の生成ですべて小文字にしてしまうため、大文字のカラム名やテーブル名を使用するために使用するのが、
spring:
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
という設定(論理名をそのまま物理名にする)になります。
ただし、Springがデフォルトで使用しているSpringImplicitNamingStrategyはHibernateのデフォルトであるImplicitNamingStrategyJpaCompliantImplをほぼそのまま使用しているため、キャメルケースのプロパティ名を使用しているとそのままキャメルケースのカラム名になってしまいます。
stackoverfrow:Hibernateの命名戦略の実例を参考に最も近いものを選んでアノテーションで調整することになるかと思います。
Hibernateの戦略を流用するだけでは足りない場合には、戦略を自分で実装することになります。
参考:
PhysicalNamingStrategyで調整
ImplicitNamingStrategyJpaCompliantImplで調整
関連エンティティとキーの実値とをそれぞれ別プロパティに持ちたい
これは問題ではなくTipsですね。
キーの実値をプロパティに保持しつつ、必要になったときだけ関連をたどりたいという都合のいいエンティティの作り方です。
解決
@JoinColumn
で関連エンティティをupdate,insert不可にする
@Entity
class Parent {
@Id
Long id;
@NatulalId
Integer key;
String firstName;
String lastName;
}
@Entity
class Child {
@Id
Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_key",referencedColumnName = "key",
insertable = false, updatable = false)
Parent parent;
@Column(name="parent_key")
Integer parentKey;
}
上記のようなクラスにすることでキーを直接保持しつつ親の参照が可能になり、子が親を更新してしまうのも防げます。
参照先を更新する際にはキーになっているフィールドの値を直接変更します(この例だとparentKey)。
マスターデータを参照するオブジェクトのとき、この形がとてもしっくりきます。
Author And Source
この問題について(SpringBoot-JPA-Hibernateのハマりメモ), 我々は、より多くの情報をここで見つけました https://qiita.com/talesleaves/items/7977274564678970c5d3著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .