簡體   English   中英

Spring Data JPA - 無限遞歸的雙向關系

[英]Spring Data JPA - bidirectional relation with infinite recursion

首先,這是我的實體。

球員

@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 調試器

有辦法解決嗎? 知道我想要不同的結果取決於端點。 例子:

  • 端點/teams/{id} => Team={id..., members=[Player={id..., team=null }]}
  • 端點/members/{id} => Player={id..., team={id..., members=null }}

謝謝!

編輯:也許這個問題不是很清楚,給出了我得到的答案,所以我會盡量更准確。

我知道可以通過 Jackson(@JSONIgnore、@JsonManagedReference/@JSONBackReference 等)或通過映射到 DTO 來防止無限遞歸。 我仍然看到的問題是:以上都是查詢后處理。 Spring JPA 返回的對象仍然是(例如)一個 Team,包含一個玩家列表,包含一個團隊,包含一個玩家列表等等。

我想知道是否有辦法告訴 JPA 或存儲庫(或任何東西)不要一遍又一遍地綁定實體內的實體?

這是我在我的項目中處理這個問題的方法。

我使用了數據傳輸對象的概念,在兩個版本中實現:完整對象和輕對象。

我將一個包含引用實體的對象定義為 List 作為Dto (僅保存可序列化值的數據傳輸對象),並將一個沒有引用實體的對象定義為Info

Info對象僅保存有關實體本身的信息,而不保存有關關系的信息。

現在,當我通過 REST API 交付Dto對象時,我只需為引用放置Info對象。

假設我通過GET /players/1 PlayerDto

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)分開,因為您沒有將實際的實體對象傳遞給接口。 為此,您將服務層內的實際實體轉換為DtoInfo 我為此使用http://modelmapper.org/ ,因為它非常簡單(一個簡短的方法調用)。

我也懶惰地獲取所有引用的實體。 我的服務方法獲取實體並將其轉換為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;

或兩者兼而有之。

就我而言,我意識到我不需要雙向(一對多-多對一)關系。

這解決了我的問題:

// Team Class:
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();

// Player Class - These three lines removed:
// @ManyToOne
// @JoinColumn(name = "pla_fk_n_teamId")
// private Team team;

Project Lombok也可能會產生此問題。 如果您使用的是 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM