Spring-启动 JPA 无限循环多对多
Spring-boot JPA infinite loop many to many
我有两个实体,它们是多对多关系。
@Entity
public class Room {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ManyToMany(mappedBy = "rooms")
private Set<Team> teams;
}
@Entity
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ManyToMany
@JoinTable(name = "teams_rooms",
joinColumns = @JoinColumn(name= "team_id"),
inverseJoinColumns = @JoinColumn(name = "room_id"))
private Set<Room> rooms;
}
为了生成数据,我有一个“房间”和“团队”的存储库:
public interface RoomRepository extends CrudRepository<Room, Long> {
}
public interface TeamRepository extends CrudRepository<Team, Long> {
}
我的目标是请求团队的所有房间,但要防止 JPA 无限循环。
@RestController
@RequestMapping("....")
public class RoomController {
@Autowired
private RoomRepository roomRepository;
@GetMapping
public Iterable<Room> getAllRoomsOfTeam() {
final long exampleId = 1; //This is just a placeholder. The id will be passed as a parameter.
final var team = teamRepository.findById(exampleId);
return ResponseEntity.ok(team);
}
}
这是结果:
{
"id": 1,
"name": "Team1",
"rooms": [
{
"id": 1,
"name": "Room 1",
"teams": [
{
"id": 1,
"name": "Team 1",
"rooms": [
{
"id": 1,
"name": "Room 1",
"teams": [
Jackson 会一直循环,直到出现异常(因为后向引用也引用了父元素,这会产生循环)。
我已经尝试过 @JsonManagedReference
和 @JsonBackReference
,但它们用于多对一关系。
如何阻止 Jackson 无限循环?我想尽可能少地影响其他存储库和查询。
目前,您的 classes 中存在循环依赖性,这导致在将对象转换为 JSON
时出现问题。请在 Team
class 中的 rooms
变量上添加 @JsonIgnore
注释,如下例所示:
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ManyToMany
@JoinTable(name = "teams_rooms",
joinColumns = @JoinColumn(name= "team_id"),
inverseJoinColumns = @JoinColumn(name = "room_id"))
@JsonIgnore
private Set<Room> rooms;
}
如果您需要双向转换的解决方案,那么您可以使用 JsonView
注释。
首先,您需要为 Team
和 Room
创建 JSON 查看配置文件,如下例所示:
public class JsonViewProfiles
{
/**
* This profile will be used while converting Team object to JSON
*/
public static class Team {}
/**
* This profile will be used while converting Room object to JSON
*/
public static class Room {}
}
使用上面创建的 JSON 查看实体中的配置文件,如下例所示:
public class Room {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@JsonView({ JsonViewProfiles.Team.class, JsonViewProfiles.Room.class })
private long id;
@JsonView(JsonViewProfiles.Room.class)
@ManyToMany(mappedBy = "rooms")
private Set<Team> teams;
}
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@JsonView({JsonViewProfiles.Team.class, JsonViewProfiles.Room.class})
private long id;
@ManyToMany
@JoinTable(name = "teams_rooms",
joinColumns = @JoinColumn(name= "team_id"),
inverseJoinColumns = @JoinColumn(name = "room_id"))
@JsonView(JsonViewProfiles.Team.class)
private Set<Room> rooms;
}
将您的对象转换为 JSON 时,请使用这些配置文件,如下例所示:
@GetMapping
public String getAllRoomsOfTeam() {
final long exampleId = 1; //This is just a placeholder. The id will be passed as a parameter.
final Team team = teamRepository.findById(exampleId);
String result = new ObjectMapper().writerWithView(JsonViewProfiles.Team.class)
.writeValueAsString(team);
return result;
}
你的控制器不应该 return entities (classes with the annotation @Entity)。最佳做法是创建另一个具有相同属性的单独 class。这段代码有一点重复,但它保持了所有层的清洁。我还建议使用@Service。
public class RoomDTO {
private String name;
private List<TeamDTO> teams = new ArrayList<>();
public RoomDTO() {
}
public RoomDTO(Room room) {
this.name = room.name;
for(Team team : room.getTeams()) {
TeamDTO teamDTO = new TeamDTO();
teamDTO.setName(team.getName);
teams.add(teamDTO);
}
}
}
public class TeamDTO {
List<RoomDTO> rooms = new ArrayList();
public TeamDTO() {
}
public TeamDTO(Team team) {
this.name = team.name;
for(Room room : team.getRooms()) {
RoomDTO roomDTO = new RoomDTO();
roomDTO.setName(team.getName);
rooms.add(roomDTO);
}
}
}
控制器应该return这个
@GetMapping
public Iterable<TeamDTO> getAllRoomsOfTeam() {
final long exampleId = 1;
final var team = teamRepository.findById(exampleId);
TeamDTO teamDTO = new TeamDTO(team);
return ResponseEntity.ok(teamDTO);
}
我有两个实体,它们是多对多关系。
@Entity
public class Room {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ManyToMany(mappedBy = "rooms")
private Set<Team> teams;
}
@Entity
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ManyToMany
@JoinTable(name = "teams_rooms",
joinColumns = @JoinColumn(name= "team_id"),
inverseJoinColumns = @JoinColumn(name = "room_id"))
private Set<Room> rooms;
}
为了生成数据,我有一个“房间”和“团队”的存储库:
public interface RoomRepository extends CrudRepository<Room, Long> {
}
public interface TeamRepository extends CrudRepository<Team, Long> {
}
我的目标是请求团队的所有房间,但要防止 JPA 无限循环。
@RestController
@RequestMapping("....")
public class RoomController {
@Autowired
private RoomRepository roomRepository;
@GetMapping
public Iterable<Room> getAllRoomsOfTeam() {
final long exampleId = 1; //This is just a placeholder. The id will be passed as a parameter.
final var team = teamRepository.findById(exampleId);
return ResponseEntity.ok(team);
}
}
这是结果:
{
"id": 1,
"name": "Team1",
"rooms": [
{
"id": 1,
"name": "Room 1",
"teams": [
{
"id": 1,
"name": "Team 1",
"rooms": [
{
"id": 1,
"name": "Room 1",
"teams": [
Jackson 会一直循环,直到出现异常(因为后向引用也引用了父元素,这会产生循环)。
我已经尝试过 @JsonManagedReference
和 @JsonBackReference
,但它们用于多对一关系。
如何阻止 Jackson 无限循环?我想尽可能少地影响其他存储库和查询。
目前,您的 classes 中存在循环依赖性,这导致在将对象转换为 JSON
时出现问题。请在 Team
class 中的 rooms
变量上添加 @JsonIgnore
注释,如下例所示:
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ManyToMany
@JoinTable(name = "teams_rooms",
joinColumns = @JoinColumn(name= "team_id"),
inverseJoinColumns = @JoinColumn(name = "room_id"))
@JsonIgnore
private Set<Room> rooms;
}
如果您需要双向转换的解决方案,那么您可以使用 JsonView
注释。
首先,您需要为 Team
和 Room
创建 JSON 查看配置文件,如下例所示:
public class JsonViewProfiles
{
/**
* This profile will be used while converting Team object to JSON
*/
public static class Team {}
/**
* This profile will be used while converting Room object to JSON
*/
public static class Room {}
}
使用上面创建的 JSON 查看实体中的配置文件,如下例所示:
public class Room {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@JsonView({ JsonViewProfiles.Team.class, JsonViewProfiles.Room.class })
private long id;
@JsonView(JsonViewProfiles.Room.class)
@ManyToMany(mappedBy = "rooms")
private Set<Team> teams;
}
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@JsonView({JsonViewProfiles.Team.class, JsonViewProfiles.Room.class})
private long id;
@ManyToMany
@JoinTable(name = "teams_rooms",
joinColumns = @JoinColumn(name= "team_id"),
inverseJoinColumns = @JoinColumn(name = "room_id"))
@JsonView(JsonViewProfiles.Team.class)
private Set<Room> rooms;
}
将您的对象转换为 JSON 时,请使用这些配置文件,如下例所示:
@GetMapping
public String getAllRoomsOfTeam() {
final long exampleId = 1; //This is just a placeholder. The id will be passed as a parameter.
final Team team = teamRepository.findById(exampleId);
String result = new ObjectMapper().writerWithView(JsonViewProfiles.Team.class)
.writeValueAsString(team);
return result;
}
你的控制器不应该 return entities (classes with the annotation @Entity)。最佳做法是创建另一个具有相同属性的单独 class。这段代码有一点重复,但它保持了所有层的清洁。我还建议使用@Service。
public class RoomDTO {
private String name;
private List<TeamDTO> teams = new ArrayList<>();
public RoomDTO() {
}
public RoomDTO(Room room) {
this.name = room.name;
for(Team team : room.getTeams()) {
TeamDTO teamDTO = new TeamDTO();
teamDTO.setName(team.getName);
teams.add(teamDTO);
}
}
}
public class TeamDTO {
List<RoomDTO> rooms = new ArrayList();
public TeamDTO() {
}
public TeamDTO(Team team) {
this.name = team.name;
for(Room room : team.getRooms()) {
RoomDTO roomDTO = new RoomDTO();
roomDTO.setName(team.getName);
rooms.add(roomDTO);
}
}
}
控制器应该return这个
@GetMapping
public Iterable<TeamDTO> getAllRoomsOfTeam() {
final long exampleId = 1;
final var team = teamRepository.findById(exampleId);
TeamDTO teamDTO = new TeamDTO(team);
return ResponseEntity.ok(teamDTO);
}