Как избежать исключения MultipleBagFetchException - PullRequest
0 голосов
/ 30 мая 2020

Добрый день всем!

В своем проекте Spring я пытаюсь избавиться от довольно популярной, на мой взгляд, ошибки - org.hibernate.loader.MultipleBagFetchException. Я хочу реализовать методы getOneClothesById(Long id) и getAllClothes().

В @ Service -слое, я хочу взять данные из Clothes (size, category, color) и отобразить их на экране и / или изменить их перед отправкой в ​​другое место .

Я прочитал пару книг и даже зашел на 4-5 страницу в Google, но полагаю, что до сих пор не нашел лучшего решения.

Люди предлагают следующие подходы для решения проблемы:

  1. Используйте Set<...> вместо List<...>
    Ссылки:

    Мой комментарий: Я считаю, что это не очень хороший подход. Вот ссылка с объяснением:

  2. Используйте Fetch.Eager вместо Fetch.Lazy или добавьте @LazyCollection(LazyCollectionOption.FALSE)
    Ссылки:
    • SO_Answer (2011)
    • SO_Answer (2015) Мой комментарий: Это не тот лучшее решение, поскольку оно не решает проблему в долгосрочной перспективе.
  3. Используйте @OrderColumn (раньше - @IndexColumn)
    Ссылки:

    Мой комментарий: Это deasls (частично ) с проблемой, но согласно документации - это исключает вариант orderBy

  4. Используйте подход Влада Михалчи: Ссылки:

    Мой комментарий: я полагаю, что это лучшее решение, которое имеет дело с org.hibernate.LazyInitializationException это его. Однако я хотел бы перенять его подход к реальности Spring Framework (2.2.6.RELEASE).

То, что я хочу:

Я хочу найти лучшую подход к решению проблемы в рамках Spring Framework. Ответ после картинки с базой данных - это решение проблемы . Однако у меня есть следующие вопросы:

  1. Мне не нравится тот факт, что я использую @Transactional, чтобы избавиться от проблемы org.hibernate.LazyInitializationException: failed to lazily initialize ...
  2. Как правильно использовать этот подход в отдельный класс, чтобы использовать его повторно? Было бы правильным решением, если бы я создал класс ClothesRepositoryFetch с аннотацией @Repository, а затем вызвал бы этот репозиторий в @Service -слое.
  3. Есть ли лучшее решение этой проблемы, используя только JPA ?

Моя база данных выглядит так:

enter image description here

Сервис одежды. java

    @Transactional
    public ResultDTO getOneClothesById(Long id){

        ResultDTO resultDTO = new ResultDTO();

        //Getting clothes with Category
        Clothes clothes = entityManager
                .createQuery(
                        "select distinct clothes " +
                                "from Clothes clothes " +
                                "left join fetch clothes.categories " +
                                "where clothes.id = :id", Clothes.class)
                .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
                .setParameter("id", id)
                .getSingleResult();

        //Getting clothes with Color
        clothes = entityManager
                .createQuery(
                        "select distinct clothes " +
                                "from Clothes clothes " +
                                "left join fetch clothes.colors color " +
                                "where clothes = :clothes", Clothes.class)
                .setParameter("clothes", clothes)
                .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
                .getSingleResult();

        //Getting clothes with Size
        clothes = entityManager
                .createQuery(
                        "select distinct clothes " +
                                "from Clothes clothes " +
                                "left join fetch clothes.sizes size " +
                                "where clothes = :clothes", Clothes.class)
                .setParameter("clothes", clothes)
                .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
                .getSingleResult();

        System.out.println(clothes.getCategories().size());
        System.out.println(clothes.getColors().size());
        System.out.println(clothes.getSizes().size());

        if (clothes != null){
            resultDTO.setResult(ResultForDTO.SUCCESS);
            resultDTO.addClothesDTO(converterClothesToDTO(clothes));
        } else {
            resultDTO.setResult(ResultForDTO.ERROR);
            resultDTO.setMessage("Невозможно найти одежду по такому ID");
        }

        return resultDTO;
    }

Код

Одежда. java:

@Entity
@Table
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Clothes {

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

    @Column
    private String name;

    @Column
    private Double price;



    /*
    Color: ManyToMany
     */
    @ManyToMany(fetch = FetchType.LAZY,
            cascade = {
                    CascadeType.PERSIST,
                    CascadeType.MERGE
            })
    @JoinTable(name = "clother_color",
            joinColumns = { @JoinColumn(name = "clother_id") },
            inverseJoinColumns = { @JoinColumn(name = "color_id") })
    private List<Color> colors = new ArrayList<>();

    public void addColor(Color color) {
        colors.add(color);
        color.getClothes().add(this);
    }

    public void removeColor(Color color) {
        colors.remove(color);
        color.getClothes().remove(this);
    }



    /*
    Size: ManyToMany
     */
    @ManyToMany(fetch = FetchType.LAZY,
            cascade = {
                    CascadeType.PERSIST,
                    CascadeType.MERGE
            })
    @JoinTable(name = "clother_size",
            joinColumns = { @JoinColumn(name = "clother_id") },
            inverseJoinColumns = { @JoinColumn(name = "size_id") })
    private List<Size> sizes = new ArrayList<>();

    public void addSize(Size size) {
        sizes.add(size);
        size.getClothes().add(this);
    }

    public void removeSize(Size size) {
        sizes.remove(size);
        size.getClothes().remove(this);
    }



    /*
    Category: ManyToMany
     */
    @ManyToMany(fetch = FetchType.LAZY,
            cascade = {
                    CascadeType.PERSIST,
                    CascadeType.MERGE
            })
    @JoinTable(name = "clother_category",
            joinColumns = { @JoinColumn(name = "clother_id") },
            inverseJoinColumns = { @JoinColumn(name = "category_id") })
    private List<Category> categories = new ArrayList<>();

    public void addCategory(Category category) {
        categories.add(category);
        category.getClothes().add(this);
    }

    public void removeCategory(Category category) {
        categories.remove(category);
        category.getClothes().remove(this);
    }


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

Категория. java:

Категория, размер, цвет имеют общую структуру, поэтому я представлю только один из них

@Entity
@Table
@NoArgsConstructor
@Getter
@Setter
public class Category {

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

    @Column
    private String name;

    @ManyToMany(mappedBy="categories")
    private List<Clothes> clothes = new ArrayList<>();

    public void addClothes(Clothes clothes) {
        this.clothes.add(clothes);
        clothes.getCategories().add(this);
    }

    public void removeClothes(Clothes clothes) {
        this.clothes.remove(clothes);
        clothes.getCategories().remove(this);
    }

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