You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
        ]
    }
]

我的困惑点

  1. 为什么comunidadesIds能正常返回关联ID,但是viviendasIds是空的?是不是@JsonIgnore注解导致序列化时忽略了整个viviendas集合,进而让DTO转换时拿不到数据?
  2. 我在Usuario的viviendas字段上加@JsonIgnore是为了避免递归(因为Vivienda里也关联了Usuario),但这样是不是反而导致关联数据没被加载?有没有更好的处理递归的方式?
  3. 另外,Usuario里的viviendas用的是FetchType.EAGER,而comunidades是LAZY,但为什么EAGER反而没加载到数据?还是说DTO转换环节出了问题?

希望大家能帮我分析下,到底是实体注解的问题,还是DTO转换的问题?应该怎么调整才能既解决递归问题,又能正确返回viviendasIds的关联数据?

备注:内容来源于stack exchange,提问作者Antonio Jesús Rodríguez Torres

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 19:13:14