投稿日:
2020年12月17日
最終更新日:
【循環参照】Lombokで生成した@Data(@ToString)でjava.lang.StackOverflowError: nullが発生する原因と解決方法【Java】
YouTubeも見てね♪
ねこじゃすり
猫を魅了する魔法の装備品!
Anker PowerCor
旅行には必須の大容量モバイルバッテリー!
【最新機種】GoPro hero11 Black
最新機種でVlogの思い出を撮影しよう!
ペヤング ソースやきそば 120g×18個
とりあえず保存食として買っておけば間違いなし!
レッドブル エナジードリンク 250ml×24本
翼を授けよう!
モンスターエナジー 355ml×24本 [エナジードリンク]
脳を活性化させるにはこれ!
ドラゴンクエスト メタリックモンスターズギャラリー メタルキング
みんな大好き経験値の塊をデスクに常備しておこう!
目次
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を組み合わせて利用する可能性がある方は是非覚えておくと良いでしょう♪