弹簧数据JPA - 无限递归的双向关系

首先,这是我的实体。

球员

@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 以多种方式避免 Web 服务中的 StackOverflowExeption。

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

有没有办法解决它?知道我想要不同的结果,具体取决于端点。例子:

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

谢谢!

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

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

我想知道是否有办法告诉JPA或存储库(或任何东西)不要一遍又一遍地绑定实体内的实体?


答案 1

以下是我在项目中处理此问题的方式。

我使用了数据传输对象的概念,在两个版本中实现:完整对象和轻量级对象。

我将包含引用实体的对象定义为 List as(仅保存可序列化值的数据传输对象),并将一个没有引用实体的对象定义为 。DtoInfo

对象仅保存有关实体本身的信息,而不保存有关关系的信息。Info

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

让我们假设我贬低了一个:PlayerDtoGET /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)分开,因为您不会将实际的实体对象传递给接口。为此,您需要将服务层内的实际实体转换为 或 。为此,我使用 http://modelmapper.org/,因为它非常简单(一个简短的方法调用)。DtoInfo

此外,我懒惰地获取所有引用的实体。我的服务方法,它获取实体并将其转换为在事务范围内运行的 there,这无论如何都是很好的做法。Dto

惰性获取

要告诉 JPA 懒惰地获取实体,只需通过定义提取类型来修改关系注释即可。此值的默认值是,在您的情况下,这是有问题的。这就是为什么你应该把它改成fetch = FetchType.EAGERfetch = 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);
}

答案 2

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

这解决了我的问题:

// 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;

龙目岛计划也可能产生此问题。尝试添加,如果您使用的是龙目岛。@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


推荐