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

标签 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

}

团队:

@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

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

  • 端点/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 或存储库(或任何东西)不要一遍又一遍地绑定(bind)实体内的实体?

最佳答案

以下是我在项目中处理这个问题的方法。

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

我将包含引用实体的对象定义为 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)分开,因为您不会将实际的实体对象传递给接口(interface)。为此,您将服务层内的实际实体转换为 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);
}

关于java - Spring Data JPA - 具有无限递归的双向关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49668298/

相关文章:

java - 加载类和实例化类的区别

java - 使用 JAX-RS 进行表单输入验证

java - 正则表达式检查加号和数字

java - 在 Spring 查询中从当前日期减去 6 个月

java - 如何防止无限递归循环

java - 打印子集的最佳递归算法

java - 过滤掉 JPA 查询中的嵌套对象

java - 模拟一个 CrudRepository findById ("111".get ()

macros - Rust 派生宏中的无限递归?

c++ - 缺少用户定义的to_string()的编译时检测