Search code examples
springspring-bootspring-data-jpaspring-data

Get Data from a Table in Spring JPA @ManyToMany relation cause StackOverFlow


i'm creating an example of @ManyToMany in Spring JPA with the with two Entities Category and Product and the entities as the following

Product.java


import jakarta.persistence.*;
import lombok.*;

import java.util.Objects;
import java.util.Set;

import org.hibernate.Hibernate;


@Table(name = "Product")
@Entity(name = "Product")
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String name;

    @Column
    private String description;

    @Column
    private double price;

    @Column
    private Integer quantity;
    
    
    
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "product_category",
            joinColumns = @JoinColumn(name = "product_id"),
            inverseJoinColumns = @JoinColumn(name = "category_id")
    )
    private Set<Category> categories;

    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
        Product product = (Product) o;
        return id != null && Objects.equals(id, product.id);
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}

Category.java


import jakarta.persistence.*;
import lombok.*;

import java.util.Objects;
import java.util.Set;

import org.hibernate.Hibernate;

@Table(name = "Category")
@Entity(name = "Category")
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Category {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String name;


    @ManyToMany(mappedBy = "categories" , fetch = FetchType.EAGER)
    private Set<Product> products;
    
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
        Category category = (Category) o;
        return id != null && Objects.equals(id, category.id);
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}

when i try to use findAll() to get all categories from CategoryRepo it gives me StackOverFlow exception

at java.base/java.lang.String.valueOf(String.java:4216) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.spi.PersistentSet.toString(PersistentSet.java:296) ~[hibernate-core-6.1.5.Final.jar:6.1.5.Final]
at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na]
at com.app.ecommerce.entity.Category.toString(Category.java:48) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:4216) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.spi.PersistentSet.toString(PersistentSet.java:296) ~[hibernate-core-6.1.5.Final.jar:6.1.5.Final]
at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na]
at com.app.ecommerce.entity.Product.toString(Product.java:63) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:4216) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.spi.PersistentSet.toString(PersistentSet.java:296) ~[hibernate-core-6.1.5.Final.jar:6.1.5.Final]
at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na]
at com.app.ecommerce.entity.Category.toString(Category.java:48) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:4216) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.spi.PersistentSet.toString(PersistentSet.java:296) ~[hibernate-core-6.1.5.Final.jar:6.1.5.Final]
at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na]
at com.app.ecommerce.entity.Product.toString(Product.java:63) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:4216) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.spi.PersistentSet.toString(PersistentSet.java:296) ~[hibernate-core-6.1.5.Final.jar:6.1.5.Final]
at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453) ~[na:na]
at com.app.ecommerce.entity.Category.toString(Category.java:48) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:4216) ~[na:na]

and i also tried to use @JsonIgnore and @JsonIgnorePropertie but it doesn't work

i would like to return all categories as the following

[
    {
       "id" : 1,
       "name" : "Mobile"
    }
]

Solution

  • The infinite recursion is between the toString methods of Product and Category. At least in one exclude the other, by

    • removing the @ToString annotation
    • using @ToString.Exclude
    • hand code the toString methods.