まず、ここに私のエンティティがあります。
プレイヤー:
@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
@ManyToOne
@JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
チーム:
@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
@OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
多くのトピックがすでに述べられているように、Jacksonを使用すると、WebServiceのStackOverflowExeptionをさまざまな方法で回避できます。
これはすばらしいことですが、JPAはシリアル化の前に、別のエンティティへの無限再帰を使用してエンティティを構築します。これは醜い問題であり、リクエストにはかなり時間がかかります。このスクリーンショットを確認してください: IntelliJ debugger
それを修正する方法はありますか?エンドポイントによって異なる結果が必要であることを知っている。例:
ありがとうございました!
編集:多分質問は私が得た答えを与えることは非常に明確ではないので、私はより正確にしようとします。
Jackson(@ JSONIgnore、@ JsonManagedReference/@ JSONBackReferenceなど)を使用するか、DTOへのマッピングを行うことで、無限再帰を防ぐことができることを知っています。私がまだ目にする問題はこれです。上記の両方がポストクエリ処理です。 Spring JPAが返すオブジェクトは、依然として(たとえば)チームであり、プレーヤーのリストが含まれている、チームが含まれている、プレーヤーのリストが含まれている、などです。
JPAまたはリポジトリ(または何か)にエンティティ内のエンティティを何度もバインドしないように指示する方法があるかどうか知りたいですか?
これが私のプロジェクトでこの問題を処理する方法です。
私は、フルオブジェクトとライトオブジェクトの2つのバージョンで実装されたデータ転送オブジェクトの概念を使用しました。
参照エンティティを含むオブジェクトをListとしてDto
(シリアル化可能な値のみを保持するデータ転送オブジェクト)として定義し、参照エンティティのないオブジェクトをInfo
として定義します。
Info
オブジェクトは、エンティティ自体に関する情報のみを保持し、関係に関する情報は保持しません。
ここで、REST APIを介してDto
オブジェクトを提供する場合、参照用にInfo
オブジェクトを配置するだけです。
私がPlayerDto
をGET /players/1
よりも配信しているとしましょう:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
TeamInfo
オブジェクトは次のようになります
public class TeamInfo {
private String teamName;
private String teamColor;
}
TeamDto
と比較
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
これにより、無限のシリアル化が回避され、残りのリソースが論理的に終了します。そうしないと、GET /player/1/team/player/1/team
を実行できるはずです。
さらに、この概念では、実際のエンティティオブジェクトをインターフェースに渡さないため、データレイヤーとクライアントレイヤー(この場合はREST API))が明確に分離されています。このために、サービスレイヤー内の実際のエンティティをDto
またはInfo
に設定します。これには http://modelmapper.org/ を使用します。メソッド呼び出し)。
また、参照されているすべてのエンティティをフェッチしますlazily。エンティティを取得してトランザクションスコープ内で実行するためにDto
に変換するサービスメソッド。これはとにかく良い方法です。
エンティティを遅延フェッチするようにJPAに指示するには、フェッチタイプを定義してリレーションシップアノテーションを変更します。これのデフォルト値はfetch = FetchType.EAGER
であり、状況によっては問題があります。そのため、fetch = FetchType.LAZY
に変更する必要があります
public class TeamEntity {
@OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
同様にPlayer
public class PlayerEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
サービスレイヤーからリポジトリメソッドを呼び出す場合、これが@Transactional
スコープ内で発生していることが重要です。そうしないと、遅延参照されたエンティティを取得できません。これは次のようになります。
@Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
@ JsonIgnoreProperties アノテーションを使用して、次のように無限ループを回避できます。
@JsonIgnoreProperties("members")
private Team team;
またはこのように:
@JsonIgnoreProperties("team")
private List<Player> members;
または両方。
私の場合、OneToMany-ManyToOne(双方向)の関係が必要ないことを理解して、これを解決しました。
このような何かが私の問題を修正しました
// Team Class:
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class:
// - I removed the 3 lines
ここに他の例とのリンクがあります: https://github.com/thombergs/code-examples/tree/master/spring-data/spring-data-rest-associations/src/main/Java/com/example/demo
プロジェクトLombokでも同じ問題が発生します。 @ ToStringと@ EqualsAndHashCodeを試してそれを修正しました:
例えば.
@Data
@Entity
@EqualsAndHashCode(exclude = { "members"}) // This,
@ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
また、ループ防止アノテーションに関する有用なガイドもあります
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion