Hibernate создает N + 1 селектов для «ромбовидных» отношений OneToMany, даже если установлены FetchType.EAGER + FetchMode.JOIN - PullRequest
0 голосов
/ 05 июля 2018

Мне нужно извлечь сложный объект с несколькими слоями подузлов до самого последнего листа (предпочтительно) в одном запросе. Каждый родитель имеет отношение @OneToMany к своим соответствующим детям.

Чтобы достичь этого, я пытался использовать FetchType.EAGER и FetchMode.JOIN для всех отношений @OneToMany, поэтому, когда я выбираю самый верхний объект, Hibernate генерирует один запрос, содержащий все дочерние элементы. Однако, к сожалению, я сталкиваюсь с проблемой N + 1. Я считаю, что это происходит потому, что отношения между моими сущностями образуют «ромбовидную форму», например:

diamond structure diagram

Когда я отображаю отношения, как показано на рисунке, Hibernate генерирует N + 1 запросов. Однако если я удалю одно из двух отношений к BOTTOM, то все будет работать нормально, и Hibernate выполнит работу в одном запросе (как и ожидалось).

Вот минимальный код для воспроизведения проблемы:

@Entity
@Table(name = "Top")
public class Top {
    @Id
    String id;

    @OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
    @Fetch(value = FetchMode.JOIN)
    Set<Left> left;

    @OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
    @Fetch(value = FetchMode.JOIN)
    Set<Right> right;
}

@Entity
@Table(name = "Left")
public class Left {
    @Id
    String id;
    String parent;

    @OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
    @Fetch(value = FetchMode.JOIN)
    Set<Bottom> bottom;
}

@Entity
@Table(name = "Right")
public class Right {
    @Id
    String id;
    String parent;

    @OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
    @Fetch(value = FetchMode.JOIN)
    Set<Bottom> bottom;
}

@Entity
@Table(name = "Bottom")
public class Bottom{
    @Id
    String id;
    String parent;
}

И код, который я использую для извлечения объекта TOP:

SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();

//Some arbitrary known "Top" object
session.get(Top.class, "1");

session.getTransaction().commit();
session.close();

Генерируемый SQL выглядит следующим образом:

SELECT top0_.id        AS id1_3_0_, 
       left1_.parent   AS parent2_1_1_, 
       left1_.id       AS id1_1_1_, 
       left1_.id       AS id1_1_2_, 
       left1_.parent   AS parent2_1_2_, 
       bottom2_.parent AS parent2_0_3_, 
       bottom2_.id     AS id1_0_3_, 
       bottom2_.id     AS id1_0_4_, 
       bottom2_.parent AS parent2_0_4_, 
       right3_.parent  AS parent2_2_5_, 
       right3_.id      AS id1_2_5_, 
       right3_.id      AS id1_2_6_, 
       right3_.parent  AS parent2_2_6_ 
FROM   Top top0_ 
       LEFT OUTER JOIN Left left1_ 
                    ON top0_.id = left1_.parent 
       LEFT OUTER JOIN Bottom bottom2_ 
                    ON left1_.id = bottom2_.parent 
       LEFT OUTER JOIN Right right3_ 
                    ON top0_.id = right3_.parent 
WHERE  top0_.id = ? 

SELECT bottom0_.parent AS parent2_0_0_, 
       bottom0_.id     AS id1_0_0_, 
       bottom0_.id     AS id1_0_1_, 
       bottom0_.parent AS parent2_0_1_ 
FROM   Bottom bottom0_ 
WHERE  bottom0_.parent = ?

Кто-нибудь знает, что нужно изменить, чтобы Hibernate генерировал один запрос вместо N + 1? Возможно, этот двойной выбор столбцов id и parent уже является признаком некоторой основной проблемы с отображением?

Кстати, я использую Hibernate 5.3.2.Final.

Заранее спасибо!

1 Ответ

0 голосов
/ 06 июля 2018

Когда вы используете @OneToMany в родительском классе (кстати, @ManyToOne намного лучшая опция) вам нужно использовать двунаправленное отношение. В противном случае у вас будет несколько запросов, как у вас. Поэтому добавьте в каждый из ваших детей класс @ManyToOne и дайте им сделать сопоставление.

например:

@Entity
@Table(name = "Top")
public class Top {
    @Id
    String id;

    @OneToMany(mappedBy = "top", fetch = FetchType.EAGER)
    List<Left> left = new ArrayList<>();

    @OneToMany(mappedBy = "top", fetch = FetchType.EAGER)
    List<Right> right = new ArrayList<>();
}

@Entity
@Table(name = "Left")
public class Left {
    @Id
    String id;
    String parent;

    @ManyToOne
    @JoinColumn(name = "top_id")
    Top top;

    @OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
    Set<Bottom> bottom;
}

@Entity
@Table(name = "Right")
public class Right {
    @Id
    String id;
    String parent;

    @ManyToOne
    @JoinColumn(name = "top_id")
    Top top;

    @OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
    Set<Bottom> bottom;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...