Исключение переполнения стека для отношений «Многие ко многим» в дочерних сущностях

#java #spring-boot #hibernate #spring-data-jpa #many-to-many

#java #spring-boot #спящий режим #spring-data-jpa #многие ко многим

Вопрос:

У меня есть объект Product, который связан с категорией с отношением ManyToOne

 public class Product {

    @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "category", nullable = false)
        @JsonIgnore
        private Category category;
    }
 

Теперь сущность категории имеет отношение «Многие ко многим» к сущности бренда.

 public class Brand {
   @ManyToMany(mappedBy = "productCompanyList")
    @JsonIgnore
    private List<Category> categories;
}

public class Category {
    @ManyToMany
    @Column(nullable = false)
    @JsonIgnore
    private List<Brand> Brands;
}
 

и, наконец, модель покупки относится к продукту

 public class Purchase {
       @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
       private Product product;
   }
 

Когда я запускаю приведенный ниже код, он генерирует исключение переполнения стека, поскольку он рекурсивно извлекает категорию и бренд бесконечно.

 List<Purchase> collect = purchaseList.stream()
                .map(a -> {
                    Product p = this.Productrepo.getById(a.getProduct().getId()).orElse(null); 
               a.setProduct(p);
                    System.out.println(p);
                    return a;
                })
                .collect(Collectors.toList());
 

Пожалуйста, дайте мне знать, есть ли способ решить эту проблему, или мне придется реструктурировать свой код.

Комментарии:

1. Это такая вещь, которая создаст исключение StackOverflow даже в тех случаях, когда вы создаете простой POJO и не имеет никакого отношения к базе данных. Простой обходной путь для этих сценариев заключается в использовании List<Identifier> вместо.

Ответ №1:

Для ManyToMany требуется таблица сопоставления. Код должен выглядеть следующим образом

 @Entity
public class Brand {
   @ManyToMany(mappedBy = "brands")
    @JsonIgnore
    private List<Category> categories;
}

@Entity
public class Category {
    @ManyToMany
    @JoinTable(
        name = "Category_Brand", 
        joinColumns = { @JoinColumn(name = "category_id") }, 
        inverseJoinColumns = { @JoinColumn(name = "brand_id") }
    )
    @JsonIgnore
    private List<Brand> brands;
}
 

mappedBy должен указывать на поле в другом объекте.

Ответ №2:

Это была первая проблема, с которой я столкнулся при сопоставлении отношений «Многие ко многим» с Hibernate, и, к сожалению, это не имеет ничего общего с сопоставлением сущностей.

Давайте на мгновение забудем о спящем режиме и проверим структуру классов.

Продукт, который имеет категорию private Category category; Категория имеет список брендов List<Brand> Brands; Бренд имеет список категорий List<Category> categories;

Когда вы это делаете System.out.println(p); , это вызывает p.toString() Это означает, что он вызывает вызов category.toString() , который затем вызывает вызов для Brands.toString() каждого бренда, имеющего список категорий, так categories.toString() называемый. Теперь каждая категория так называется список брендов brands.toString() . И цикл продолжается.

Я понимаю, что вы использовали @JsonIgnore , чтобы избежать этой проблемы при сериализации объекта, но при выполнении toString() выполняются рекурсивные вызовы.

Могут быть и другие способы решения этой проблемы, но ниже приведены два способа, которыми я ее решил:

  • Переопределите toString() метод и исключите список

Вы можете явно переопределить toString() метод и исключить список из состава строки, перенастроенной с помощью toString() по умолчанию

Если вы используете lombok для генерации toString() , вы можете аннотировать эти списки, @ToString.Exclude чтобы исключить их из числа строк, перенастроенных методом toString(), сгенерированным Lombok.

 public class Brand {
   @ToString.Exclude    
   @ManyToMany(mappedBy = "productCompanyList")
    @JsonIgnore
    private List<Category> categories;
}

public class Category {
    @ManyToMany
    @Column(nullable = false)
    @ToString.Exclude
    @JsonIgnore
    private List<Brand> Brands;
}