Solución:
Así es como manejo este problema en mis proyectos.
Utilicé el concepto de objetos de transferencia de datos, implementado en dos versiones: un objeto completo y un objeto ligero.
Defino un objeto que contiene las entidades referenciadas como Lista como Dto
(objeto de transferencia de datos que solo contiene valores serializables) y defino un objeto sin las entidades referenciadas como Info
.
A Info
El objeto solo contiene información sobre la propia entidad y no sobre las relaciones.
Ahora cuando entrego un Dto
objeto sobre una API REST, simplemente puse Info
objetos para las referencias.
Supongamos que entrego un PlayerDto
sobre GET /players/1
:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Mientras que el TeamInfo
el objeto se parece a
public class TeamInfo {
private String teamName;
private String teamColor;
}
comparado con un TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
Esto evita una serialización interminable y también crea un final lógico para sus recursos de descanso, ya que de lo contrario debería poder GET /player/1/team/player/1/team
Además, el concepto separa claramente la capa de datos de la capa del cliente (en este caso, la API REST), ya que no pasa el objeto de entidad real a la interfaz. Para esto, convierte la entidad real dentro de su capa de servicio en un Dto
o Info
. Utilizo http://modelmapper.org/ para esto, ya que es muy fácil (una breve llamada al método).
También busco todas las entidades referenciadas perezosamente. Mi método de servicio que obtiene la entidad y la convierte a la Dto
allí para ejecuciones dentro del alcance de una transacción, lo cual es una buena práctica de todos modos.
Ir a buscar perezoso
Para decirle a JPA que busque una entidad de forma perezosa, simplemente modifique la anotación de su relación definiendo el tipo de búsqueda. El valor predeterminado para esto es fetch = FetchType.EAGER
que en tu situación es problemática. Por eso deberías cambiarlo a fetch = FetchType.LAZY
public class TeamEntity {
@OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Asimismo el Player
public class PlayerEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
Al llamar a su método de repositorio desde su capa de servicio, es importante que esto suceda dentro de un @Transactional
alcance, de lo contrario, no podrá obtener la entidad a la que se hace referencia perezosamente. Que se vería así:
@Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
Puede usar la anotación @JsonIgnoreProperties para evitar un bucle infinito, como este:
@JsonIgnoreProperties("members")
private Team team;
o así:
@JsonIgnoreProperties("team")
private List<Player> members;
o ambos.
En mi caso, me di cuenta de que no necesitaba una relación (bidireccional) OneToMany-ManyToOne.
Esto solucionó mi problema
// 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;
Proyecto Lombok también podría producir este problema. Intenta agregar @Encadenar y @EqualsAndHashCode si es el caso.
@Data
@Entity
@EqualsAndHashCode(exclude = { "members"}) // This,
@ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
Buena guía en anotaciones de recursividad infinitas
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion