Избегайте исключения MultipleBagFetchException в @ManyToMany - PullRequest
0 голосов
/ 09 мая 2020

Перерыл весь Inte rnet и пока не нашел подходящего для себя ответа. Я хотел бы применить фильтр к своему проекту: пользователь вводит список цветов и список размеров, и программа возвращает все параметры на основе ввода.

Для этого я использую запрос:

    @Query("select distinct clothes FROM Clothes as clothes left join fetch clothes.colors as ccolor left join fetch clothes.sizes as csize where " +
            "ccolor.name in (:colors) and csize.name in (:sizes)")
    List<Clothes> findAllByColorsInAndSizesIn(List<String> colors, List<String> sizes);

Я видел множество ответов участников, что лучше использовать Set<...> вместо List<...>, но я считаю, что это неправда (есть статья об этом от Влада Михалчи ). Использование Set<...> похоже на использование @Transactional для прохождения тестов в вашем проекте Spring.


Из упомянутой выше статьи я попытался использовать следующий код в зависимости от моей проблемы:
        List<Clothes> clothes = entityManager
                .createQuery(
                        "select distinct clothes FROM Clothes as clothes left join fetch clothes.colors as ccolor where " +
                                " ccolor.name in (:colors)", Clothes.class)
                .setParameter("colors", colors)
                .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
                .getResultList();
        clothes = entityManager
                .createQuery(
                        "select distinct clothes FROM Clothes as clothes left join fetch clothes.sizes as csize where " +
                        "csize.name in (:sizes)", Clothes.class)
                .setParameter("sizes", sizes)
                .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
                .getResultList();

Однако я не думаю, что это правильно, поскольку первый запрос избыточен. Вот доказательство: enter image description here

Итак, мне пришла в голову идея «вручную» сравнить два массива и объединить их, где размер и цвет одежды соответствуют исходному фильтру.

Мой UML:

UML

Код:

Одежда. java:

@Entity
@Table(name = "clothes")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Clothes {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "clothes_generator")
    @SequenceGenerator(name="clothes_generator", sequenceName = "clothes_seq", allocationSize = 1, initialValue = 1)
    private Long id;

    @Column
    private String name;

    @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);
    }

    @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);
    }
}

Размеры. java:

@Entity
@Table(name = "color")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Color {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "color_generator")
    @SequenceGenerator(name="color_generator", sequenceName = "color_seq", allocationSize = 1, initialValue = 1)
    private Long id;

    @Column
    private String name;

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

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

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

    public Color(String name){
        this.name = name;
    }
}

Цвет. java

@Entity
@Table(name = "color")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Color {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "color_generator")
    @SequenceGenerator(name="color_generator", sequenceName = "color_seq", allocationSize = 1, initialValue = 1)
    private Long id;

    @Column
    private String name;

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

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

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

    public Color(String name){
        this.name = name;
    }
}

Тест, который я пытаюсь пройти (я знаю, что мне нужно использовать assert):

    @Test
    void findClothesByColorsAndSizes(){

        Color colorBlue = new Color("Blue");
        Color colorRed = new Color("Red");
        Color colorPink = new Color("Pink");
        colorRepository.saveAll(Arrays.asList(colorBlue, colorPink, colorRed));
        colorRepository.flush();

        Size sizeL = new Size("L");
        Size sizeXL = new Size("XL");
        Size sizeS = new Size("S");
        sizeRepository.saveAll(Arrays.asList(sizeL, sizeS, sizeXL));
        sizeRepository.flush();

        Clothes clothes1 = new Clothes("Clothes1");
        clothes1.addColor(colorRepository.findByName(colorBlue.getName()));
        clothes1.addSize(sizeRepository.findByName(sizeS.getName()));

        Clothes clothes2 = new Clothes("Clothes2");
        clothes2.addColor(colorRepository.findByName(colorBlue.getName()));
        clothes2.addSize(sizeRepository.findByName(sizeL.getName()));

        Clothes clothes3 = new Clothes("Clothes3");
        clothes3.addColor(colorRepository.findByName(colorRed.getName()));
        clothes3.addSize(sizeRepository.findByName(sizeXL.getName()));

        Clothes clothes4 = new Clothes("Clothes4");
        clothes4.addColor(colorRepository.findByName(colorPink.getName()));
        clothes4.addSize(sizeRepository.findByName(sizeS.getName()));

        Clothes clothes5 = new Clothes("Clothes5");
        clothes5.addColor(colorRepository.findByName(colorPink.getName()));
        clothes5.addSize(sizeRepository.findByName(sizeL.getName()));


        clothesRepository.saveAll(Arrays.asList(clothes1, clothes2, clothes3, clothes4, clothes5));
        clothesRepository.flush();

        for (Clothes clothes: clothesRepository.findAll()){
            System.out.println("name: " + clothes.getName());
            System.out.println("color: " + clothes.getColors().get(0).getName());
            System.out.println("size: " + clothes.getSizes().get(0).getName());
            System.out.println();
        }

        System.out.println();
        System.out.println();
        System.out.println();

        List<String> colors = new ArrayList<>();
        colors.add(colorBlue.getName());
        colors.add(colorRed.getName());

        List<String> sizes = new ArrayList<>();
        sizes.add(sizeL.getName());
        sizes.add(sizeXL.getName());

//        List<Clothes> clothes = clothesRepository.findAllBySizesIn(sizes);
//        List<Clothes> common = new ArrayList<>();
//        for(Clothes clothesItem : clothesRepository.findAllByColorsIn(colors)){
//            if(clothes.contains(clothesItem)){
//                common.add(clothesItem);
//            }
//        }

//        System.out.println(common.size());
//
//        System.out.println();
//        for (Clothes clothesItem : common) {
//            System.out.println("name: " + clothesItem.getName());
//            System.out.println("color: " + clothesItem.getColors().size());
//            System.out.println("size: " + clothesItem.getSizes().size());
//            System.out.println();
//        }

        List<Clothes> clothes = entityManager
                .createQuery(
                        "select distinct clothes FROM Clothes as clothes left join fetch clothes.colors as ccolor where " +
                                " ccolor.name in (:colors)", Clothes.class)
                .setParameter("colors", colors)
                .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
                .getResultList();
        clothes = entityManager
                .createQuery(
                        "select distinct clothes FROM Clothes as clothes left join fetch clothes.sizes as csize where " +
                        "csize.name in (:sizes)", Clothes.class)
                .setParameter("sizes", sizes)
                .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
                .getResultList();


        System.out.println(clothes.size());

        System.out.println();
        for (Clothes clothesItem : clothes) {
            System.out.println("name: " + clothesItem.getName());
            System.out.println("color: " + clothesItem.getColors().size());
            System.out.println("size: " + clothesItem.getSizes().size());
            System.out.println();
        }
    }
...