Spring Data JPA-具有无限递归的双向关系

Mic*_*nès 8 java spring-data-jpa 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

}
Run Code Online (Sandbox Code Playgroud)

团队

@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, 
property="id")
public class Team {

    // other fields

    @OneToMany(mappedBy = "team")
    private List<Player> members;

    // methods

}
Run Code Online (Sandbox Code Playgroud)

正如已经提到的许多主题一样,使用Jackson可以通过多种方式避免WebService中的StackOverflowExeption。

太酷了,除了JPA之外,所有其他人仍然在序列化之前构造具有无限递归到另一个实体的实体。这只是丑陋的回答,要求花费更长的时间。检查此屏幕截图:IntelliJ调试器

有办法解决吗?知道我要根据端点获得不同的结果。例子 :

  • 端点/ teams / {id} => Team = {id ...,成员= [Player = {id ...,team = null }]}
  • 端点/ members / {id} =>玩家= {id ...,团队= {id ...,成员=空 }}

谢谢!

编辑:也许给出我得到的答案的问题不是很清楚,所以我将尝试更加精确。

我知道可以通过Jackson(@ JSONIgnore,@ JsonManagedReference / @ JSONBackReference等)或通过对DTO进行一些映射来防止无限递归。我仍然看到的问题是:以上两个都是查询后处理。Spring JPA返回的对象仍然是(例如)一个Team,包含一个玩家列表,一个团队,包含一个玩家列表,等等。

我想知道是否有一种方法可以告诉JPA或存储库(或任何东西)一次又一次不绑定实体内的实体?

use*_*932 8

就我而言,我意识到我不需要双向(一对多多对一)关系。

这解决了我的问题:

// 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;
Run Code Online (Sandbox Code Playgroud)

Lombok项目也可能会产生此问题。如果您使用的是 Lombok,请尝试添加@ToString和。@EqualsAndHashCode

@Data
@Entity

@EqualsAndHashCode(exclude = { "members"}) // This,
@ToString(exclude = { "members"}) // and this

public class Team implements Serializable {

// ...


Run Code Online (Sandbox Code Playgroud)

这是关于无限递归注释的很好的指南https://www.baeldung.com/jackson-biorient-relationships-and-infinite-recursion


Cep*_*pr0 6

您可以使用@JsonIgnoreProperties注释来避免无限循环,如下所示:

@JsonIgnoreProperties("members")
private Team team;
Run Code Online (Sandbox Code Playgroud)

或者像这样:

@JsonIgnoreProperties("team")
private List<Player> members;
Run Code Online (Sandbox Code Playgroud)

或两者。


Her*_*erb 6

这是我在项目中处理此问题的方法。

我使用了数据传输对象的概念,有两种版本实现:完整对象和轻型对象。

我将包含引用实体的对象定义为List为Dto(仅包含可序列化值的数据传输对象),并且将不包含引用实体的对象定义为Info

一个Info对象只能容纳约十分实体本身,而不是关于关系的信息。

现在,当我Dto通过REST API 交付对象时,我只需将Info对象作为引用即可。

让我们假设我PlayerDto结束了GET /players/1

public class PlayerDto{
   private String playerName;
   private String playercountry;
   private TeamInfo;
}
Run Code Online (Sandbox Code Playgroud)

TeamInfo对象看起来像

public class TeamInfo {
    private String teamName;
    private String teamColor;
}
Run Code Online (Sandbox Code Playgroud)

TeamDto

public class TeamDto{
    private String teamName;
    private String teamColor;
    private List<PlayerInfo> players;
}
Run Code Online (Sandbox Code Playgroud)

这样可以避免无休止的序列化,并且还可以为您的其余资源提供合理的解决方案,否则您应该能够 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;
}
Run Code Online (Sandbox Code Playgroud)

同样的 Player

public class PlayerEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "pla_fk_n_teamId")
    private TeamEntity team;
}
Run Code Online (Sandbox Code Playgroud)

从服务层调用存储库方法时,很重要的一点是,这是在@Transactional范围内发生的,否则,您将无法获得延迟引用的实体。看起来像这样:

 @Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
    TeamEntity entity= teamRepository.getTeamByName(teamName);
    return modelMapper.map(entity,TeamDto.class);
}
Run Code Online (Sandbox Code Playgroud)