Spring-boot JPA无限循环多对多
Posted
技术标签:
【中文标题】Spring-boot JPA无限循环多对多【英文标题】:Spring-boot JPA infinite loop many to many 【发布时间】:2021-08-25 09:38:13 【问题描述】:我有两个实体,它们是多对多关系。
@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
,但它们用于多对一关系。
如何阻止杰克逊无限循环?我想尽可能少地影响其他存储库和查询。
【问题讨论】:
如果您没有需要获取所有使用房间的团队的用例(通过房间变量获取团队),您不需要多对多但一对多的关系。在继续对您的问题发表评论之前,您是否考虑过这种方法? 你也可以查看this baeldung 的文章。 @Aethernite 我考虑过这种方法。但是,我需要双向获取,这意味着我需要双向的多对多关系。我已经阅读了这篇文章,但它是关于与反向引用的一对多关系,我也有(但在我遵循这些步骤后它们工作正常)。 您可以在此链接***.com/a/47118424/8986786找到解决方案 【参考方案1】:目前,您的类中存在循环依赖,这在将对象转换为 JSON
时会导致问题。请在Team
类中的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;
【讨论】:
这行得通。但是如果我想获取一个团队的所有房间怎么办? Jackson 会忽略它,因为该变量带有@JsonIgnore
注释。
您要执行“房间到团队”和“团队到房间”等双向转换吗?请确认!
更新了双向转换的答案
很好的例子,谢谢!接受的答案对我有用,因为我还需要在控制器中附加自定义变量,这些变量不应该在数据库中(我显然可以在 DTO 中这样做)。但是您的回答是@JsonView
的一个很好的例子,谢谢!【参考方案2】:
您的控制器不应返回 entities(带有注释 @Entity 的类)。最佳实践是创建另一个具有相同属性的单独类。这段代码有一些重复,但它使所有层保持干净。我也建议使用@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);
控制器应该返回这个
@GetMapping
public Iterable<TeamDTO> getAllRoomsOfTeam()
final long exampleId = 1;
final var team = teamRepository.findById(exampleId);
TeamDTO teamDTO = new TeamDTO(team);
return ResponseEntity.ok(teamDTO);
How to use DTOs in the Controller, Service and Repository pattern
【讨论】:
感谢您提供这些有用的信息!但这不会解决我的循环问题,对吧? 它会解决这个问题,因为您只创建了 1 级递归并且不会永远循环。以上是关于Spring-boot JPA无限循环多对多的主要内容,如果未能解决你的问题,请参考以下文章
JPA Spring Boot 微服务 - 使用两个多对一映射持久化实体时的无限循环
在 Swift 中将 NSManagedObject 添加到 CoreData 多对多关系时防止循环