ねこじゃすり
猫を魅了する魔法の装備品!
Anker PowerCor
旅行には必須の大容量モバイルバッテリー!
【最新機種】GoPro hero11 Black
最新機種でVlogの思い出を撮影しよう!
[ノースフェイス] THE NORTH FACE メンズ アウター マウンテンライトジャケット
防水暴風で耐久性抜群なので旅行で大活躍です!
ペヤング ソースやきそば 120g×18個
とりあえず保存食として買っておけば間違いなし!
MOFT X 【新型 ミニマム版】 iPhone対応 スマホスタンド
Amazon一番人気のスマホスタンド!カード類も収納出来てかさ張らないのでオススメです!
サンディスク microSD 128GB
スマホからSwitchまで使える大容量MicroSDカード!
スポンサーリンク
目次
JPAオブジェクトのLombokでStackOverflowErrorが発生
循環参照が発生しメモリリーク
JPA
+Lombok
を組み合わせて利用している際に、以下のようなJPAで取得した親子関係を持つEntityオブジェクトを標準出力や文字列化、エラーログ出力時にそのまま渡してtoString()
メソッドを呼び出した際にjava.lang.StackOverflowError: null
が発生する場合があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package entities.account; import java.time.LocalDateTime; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "accounts") @NoArgsConstructor @Data @Builder @AllArgsConstructor public class AccountEntity{ @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Long id; @OneToOne @JoinColumn(name = "id", referencedColumnName = "accountId") public AccountStatusEntity accountStatusEntity; @Column private String name; @Column private LocalDateTime createdAt; @Column private LocalDateTime updatedAt; @Column private LocalDateTime deletedAt; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package entities.account; import java.io.Serializable; import java.time.LocalDateTime; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "account_status") @NoArgsConstructor @Data @Builder @AllArgsConstructor public class AccountStatusEntity implements Serializable { @Id @OneToOne @JoinColumn(name = "accountId", referencedColumnName = "id") public AccountEntity accountEntity; @Column private AccountStatusEnum status; @Column private LocalDateTime createdAt; @Column private LocalDateTime updatedAt; } |
1 2 3 4 5 6 7 8 9 10 |
Caused by: java.lang.StackOverflowError: null at entities.account.AccountStatusEntity.toString(AccountStatusEntity.java:21) at java.base/java.lang.String.valueOf(String.java:2951) at entities.account.AccountEntity.toString(AccountEntity.java:22) at java.base/java.lang.String.valueOf(String.java:2951) at entities.account.AccountStatusEntity.toString(AccountStatusEntity.java:21) at java.base/java.lang.String.valueOf(String.java:2951) at entities.account.AccountEntity.toString(AccountEntity.java:22) at java.base/java.lang.String.valueOf(String.java:2951) ...(以下無限ループ) |
ログを見てもらうとわかる通り、toString
を実行した際に依存している子モデルのtoString
が呼ばれ、その中のさらに依存している親モデルのtoString
が呼ばれ・・・という形で無限ループしてしまっています。
解決方法
@ToString.Excludeを使う
この現象を避けるには、子Entity側のtoString
で親EntityのtoString
を呼ばないようにする必要があります。
幸い、Lombok
では上記を実装するための便利なアノテーションがあるのでそちらを利用しましょう。
toString
から除外したいフィールドに以下をつけるだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
package entities.account; import java.io.Serializable; import java.time.LocalDateTime; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; // ★追加 @Entity @Table(name = "account_status") @NoArgsConstructor @Data @Builder @AllArgsConstructor public class AccountStatusEntity implements Serializable { @ToString.Exclude // ★追加 @Id @OneToOne @JoinColumn(name = "accountId", referencedColumnName = "id") public AccountEntity accountEntity; @Column private AccountStatusEnum status; @Column private LocalDateTime createdAt; @Column private LocalDateTime updatedAt; } |
これで循環参照がされなくなるので、正常にtoString
メソッドを呼び出す事が可能になりました。
@ToString(exclude = {“XXX”})は非推奨
ちなみにクラスに対して@ToString(exclude = {"除外対象の親Entityプロパティ名"})
とする事でも同様の処理をする事が可能だが、こちらの記述方法は非推奨のためいずれ無くなるらしいので使わないことをオススメします。
終わりに
以上のように循環参照の恐怖を知っていないと、サーバーが落ちてしまう原因の一つとなってしまいます。
LombokとJPAを組み合わせて利用する可能性がある方は是非覚えておくと良いでしょう♪