Spring Boot + JPA 递归问题排查及Usuario返回viviendasIds为空的解决求助
Spring Boot + JPA 递归问题排查及Usuario返回viviendasIds为空的解决求助
大家好,我现在在开发一个基于Spring Boot的社区管理类应用,目前遇到了两个头疼的问题:一是JPA实体之间的递归加载导致的序列化问题,二是查询用户列表时,viviendasIds字段一直返回空数组,但是comunidadesIds却能正常返回关联的ID列表。我已经尝试了一些注解处理递归,但还是没搞定,想请大家帮忙看看问题出在哪。
问题细节
- 核心涉及
Usuario和Vivienda两个实体,是双向多对多关联关系 - 调用查询所有用户的接口时,返回的JSON里
viviendasIds始终是空数组,但数据库中明明存在关联数据 - 为处理递归问题,我在实体上添加了
@JsonIgnore、@JsonIgnoreProperties等注解,但似乎没解决根本问题,反而可能导致关联数据无法正常返回
相关代码
Usuario 实体
package com.antonio.comunidad.security.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; import lombok.ToString; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; @Entity @Table(name = "usuarios") @Data public class Usuario implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String nombre; @Column(nullable = false) private String apellido; @Column(nullable = false) @NotBlank @Size(min = 6,max =20) private String password; @Column(nullable = false, unique = true) private String email; @Column(nullable = false) private boolean enabled; // Un usuario puede estar en varias comunidades y una comunidad tiene varios usuarios @ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "usuario_comunidad", joinColumns = @JoinColumn(name = "usuario_id"), inverseJoinColumns = @JoinColumn(name = "comunidad_id") ) private Set<Comunidad> comunidades; // Relación ManyToMany con Vivienda @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "usuario_vivienda", joinColumns = @JoinColumn(name = "usuario_id"), inverseJoinColumns = @JoinColumn(name = "vivienda_id") ) @JsonIgnore private Set<Vivienda> viviendas; // Relación ManyToMany con Roles @ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "usuario_roles", joinColumns = @JoinColumn(name = "usuario_id"), inverseJoinColumns = @JoinColumn(name = "rol_id") ) @Fetch(FetchMode.JOIN) private Set<Rol> roles; // Implementación de UserDetails para Spring Security @Override public Collection<? extends GrantedAuthority> getAuthorities() { return roles != null ? roles.stream() .map(rol -> new SimpleGrantedAuthority(rol.getNombre())) .collect(Collectors.toSet()) : Set.of(); } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return this.enabled; } }
Vivienda 实体
package com.antonio.comunidad.security.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.*; import java.util.List; import java.util.Set; import java.util.Optional; @Entity @Data @Table(name = "vivienda") @NoArgsConstructor @AllArgsConstructor @Builder public class Vivienda { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String bloque; private String piso; private String puerta; private Boolean alquilado; private Boolean alCorrientePago; // Relación Muchos a Uno con Comunidad (Cada vivienda pertenece a una comunidad) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "comunidad_id") @JsonIgnoreProperties({"viviendas"}) // Evita la carga recursiva infinita @ToString.Exclude // Evita la recursión infinita private Comunidad comunidad; /** * -- GETTER -- * Método para obtener los propietarios, forzando la carga de datos. */ // Relación Muchos a Muchos con Usuario (Cada vivienda puede tener varios propietarios) @ManyToMany(mappedBy = "viviendas", fetch = FetchType.LAZY) private Set<Usuario> usuarios; // Relación Uno a Muchos con Pagos @OneToMany(mappedBy = "vivienda", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Pago> pagos; /** * Método para verificar si la vivienda está al corriente de pago. */ public boolean estaAlCorriente() { return Optional.ofNullable(pagos) .map(listaPagos -> listaPagos.stream().allMatch(Pago::isRealizado)) .orElse(true); // Si no hay pagos, asumimos que está al corriente } }
控制器与返回结果
接口控制器代码
@GetMapping public List<UsuarioDTO> getAllUsuarios() { return usuarioService.getAllUsuarios(); }
接口返回JSON
[ { "id": 1, "username": "antonio", "password": null, "nombre": "Antonio", "apellido": "Pedro", "email": "email@ejemplo.com", "enabled": true, "rolesIds": [ 2, 1 ], "viviendasIds": [], "comunidadesIds": [ 1, 2 ] } ]
我的困惑点
- 为什么
comunidadesIds能正常返回关联ID,但是viviendasIds是空的?是不是@JsonIgnore注解导致序列化时忽略了整个viviendas集合,进而让DTO转换时拿不到数据? - 我在
Usuario的viviendas字段上加@JsonIgnore是为了避免递归(因为Vivienda里也关联了Usuario),但这样是不是反而导致关联数据没被加载?有没有更好的处理递归的方式? - 另外,
Usuario里的viviendas用的是FetchType.EAGER,而comunidades是LAZY,但为什么EAGER反而没加载到数据?还是说DTO转换环节出了问题?
希望大家能帮我分析下,到底是实体注解的问题,还是DTO转换的问题?应该怎么调整才能既解决递归问题,又能正确返回viviendasIds的关联数据?
备注:内容来源于stack exchange,提问作者Antonio Jesús Rodríguez Torres
相关产品推荐
相关产品推荐

