Spring 数据 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 中的 WhosebugExeption。
这很酷,除了 JPA 之外,其他所有实体在序列化之前仍然构造一个无限递归到另一个实体的实体。这很丑陋,请求需要更长的时间。检查此屏幕截图:IntelliJ debugger
有办法解决吗?知道我想要不同的结果取决于端点。示例:
- 端点/teams/{id} => Team={id..., members=[Player={id..., team=空}]}
- endpoint /members/{id} => Player={id..., team={id..., members=null}}
谢谢!
编辑:也许问题不是很清楚给出我得到的答案所以我会尝试更精确。
我知道可以使用 Jackson(@JSONIGnore、@JsonManagedReference/@JSONBackReference 等)或通过映射到 DTO 来防止无限递归。我仍然看到的问题是:以上都是 post-query 处理。 Spring JPA returns 的对象仍然是(例如)一个 Team,包含一个球员列表,包含一个团队,包含一个球员列表,等等
我想知道是否有办法告诉 JPA 或存储库(或任何东西)不要一遍又一遍地绑定实体内的实体?
您可以使用 @JsonIgnoreProperties 注释来避免无限循环,如下所示:
@JsonIgnoreProperties("members")
private Team team;
或者像这样:
@JsonIgnoreProperties("team")
private List<Player> members;
或两者。
下面是我在项目中处理这个问题的方法。
我使用了数据传输对象的概念,实现了两个版本:完整对象和轻对象。
我将包含引用实体的对象定义为 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)分开,因为您不会将实际的实体对象传递给接口。为此,您将服务层内的实际实体转换为 Dto
或 Info
。我为此使用 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);
}
就我而言,我意识到我不需要双向(一对 Many-Many 一对)关系。
这解决了我的问题:
// 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
首先,这是我的实体。
玩家:
@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 中的 WhosebugExeption。
这很酷,除了 JPA 之外,其他所有实体在序列化之前仍然构造一个无限递归到另一个实体的实体。这很丑陋,请求需要更长的时间。检查此屏幕截图:IntelliJ debugger
有办法解决吗?知道我想要不同的结果取决于端点。示例:
- 端点/teams/{id} => Team={id..., members=[Player={id..., team=空}]}
- endpoint /members/{id} => Player={id..., team={id..., members=null}}
谢谢!
编辑:也许问题不是很清楚给出我得到的答案所以我会尝试更精确。
我知道可以使用 Jackson(@JSONIGnore、@JsonManagedReference/@JSONBackReference 等)或通过映射到 DTO 来防止无限递归。我仍然看到的问题是:以上都是 post-query 处理。 Spring JPA returns 的对象仍然是(例如)一个 Team,包含一个球员列表,包含一个团队,包含一个球员列表,等等
我想知道是否有办法告诉 JPA 或存储库(或任何东西)不要一遍又一遍地绑定实体内的实体?
您可以使用 @JsonIgnoreProperties 注释来避免无限循环,如下所示:
@JsonIgnoreProperties("members")
private Team team;
或者像这样:
@JsonIgnoreProperties("team")
private List<Player> members;
或两者。
下面是我在项目中处理这个问题的方法。
我使用了数据传输对象的概念,实现了两个版本:完整对象和轻对象。
我将包含引用实体的对象定义为 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)分开,因为您不会将实际的实体对象传递给接口。为此,您将服务层内的实际实体转换为 Dto
或 Info
。我为此使用 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);
}
就我而言,我意识到我不需要双向(一对 Many-Many 一对)关系。
这解决了我的问题:
// 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