Al desarrollar aplicaciones con Spring Boot y JPA, es habitual definir relaciones bidireccionales entre entidades (como One-to-Many o Many-to-Many). Un problema recurrente surge cuando se utiliza la anotación @Data de Lombok. Esta anotación genera automáticamente los métodos toString(), equals() y hashCode(), los cuales intentan acceder a todos los campos de la clase, incluyendo las relaciones con otras entidades.
Si la Entidad A referencia a la Entidad B, y la Entidad B referencia de vuelta a la Entidad A, el método toString() generado entrará en un ciclo de llamadas recursivas infinitas, lo que finalmente provoca un error de tipo StackOverflowError. La solución consiste en evitar las anotaciones que generan estos métodos de forma masiva o personalizar la implementación de toString() para romper la cadena de referencias.
Estrategia de solución
Para mitigar este fallo, se recomienda sustituir @Data por anotaciones más granulares como @Getter y @Setter. Además, se debe sobrescribir manualmente el método toString(), asegurándose de no incluir el objeto relacionado completo, sino únicamente un identificador o una propiedad simple (como el tamaño de una colección).
Ejemplo de implementación: Entidad Departamento
En este ejemplo, la entidad Departamento tiene una relación de uno a muchos con Empleado. En el método toString(), solo imprimimos la cantidad de empleados para evitar la recursión.
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.List;
@Getter
@Setter
@Entity
@Table(name = "tbl_departamento")
public class Departamento extends EntidadBase {
@Column(name = "dep_codigo", length = 20)
private String codigo;
@Column(name = "dep_nombre", length = 100)
private String nombre;
@OneToMany(mappedBy = "departamento", cascade = CascadeType.ALL)
private List<Empleado> listaEmpleados;
@Override
public String toString() {
return "Departamento{" +
"id=" + id +
", codigo='" + codigo + '\'' +
", nombre='" + nombre + '\'' +
", totalEmpleados=" + (listaEmpleados != null ? listaEmpleados.size() : 0) +
", auditData=" + super.toString() +
'}';
}
}
Ejemplo de implemenatción: Entidad Empleado
La entidad Empleado mantiene una referencia a su Departamento. Para evitar el bucle, el método toString() del empleado no debe llamar al toString() del departamento de forma directa.
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Getter
@Setter
@Entity
@Table(name = "tbl_empleado")
public class Empleado extends EntidadBase {
@Column(name = "emp_identificacion", length = 15)
private String identificacion;
@Column(name = "emp_apellido", length = 50)
private String apellido;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "departamento_id")
private Departamento departamento;
@Override
public String toString() {
return "Empleado{" +
"id=" + id +
", identificacion='" + identificacion + '\'' +
", apellido='" + apellido + '\'' +
", perteneceADepartamento=" + (departamento != null ? departamento.getCodigo() : "N/A") +
'}';
}
}
Al implementar los métodos de esta manera, se garantiza que la representación en cadena de los objetos sea segura para la memoria y el rendimiento del sistema, eliminando el riesgo de desbordamiento de pila durante operaciones de depuración o regitsro de logs.