Почему использование @EntityGraph вместе с JOIN приводит к ошибке в одном случае, но работает нормально в другом?

При загрузке сущностей из базы данных я использую @NamedEntityGraph. Я пытался использовать граф вот в таком запросе:

@EntityGraph(value = "paragraph-graph-questions")
@Query("SELECT sp FROM Paragraph p JOIN p.subParagraphs sp WHERE p.id=:id")
Set<Paragraph> getAllSubParagraphs(int id);

Это вызывает следующую ошибку:

org.hibernate.QueryException: query specified join fetching, 
but the owner of the fetched association was not present in the select list ...

Следует обратить внимание, что запрос в данном случае рефлексивен, потому что сущность Paragraph ссылается на другие сущности Paragraph отношением OneToMany:

@NamedEntityGraph(
        name = "paragraph-graph-questions",
        attributeNodes = {
                @NamedAttributeNode(value = "questions")
        })
@Entity
@Table(name = "paragraphs")
public class Paragraph {

    @Id
    private Integer id

    @OneToMany(mappedBy = "paragraph")
    private Set<Question> questions;

    @OneToMany
    @JoinTable(
            name = "sub_paragraphs",
            joinColumns = @JoinColumn(name = "paragraph_id"),
            inverseJoinColumns = @JoinColumn(name = "sub_paragraph_id")
    )
    private Set<Paragraph> subParagraphs;
}

Я решил, что нельзя использовать JOIN и EntityGraph одновременно, так как это приводит к ошибке. Но, видимо, всё не так однозначано, так как вот такой запрос корректно работает с графом сущностей:

@EntityGraph(value = "report-graph-low")
@Query("""
        SELECT r FROM Report r 
        LEFT JOIN r.themeDetails td ON td.theme.id=:themeId 
        LEFT JOIN r.paragraphDetails pd ON pd.paragraph.theme.id=:themeId 
        LEFT JOIN r.offlineThemeDetails od ON od.theme.id=:themeId 
        WHERE r.student.id=:studentId
        """)
List<Report> getAllByStudentIdAndThemeId(int studentId, int themeId, Pageable pagination);

В случае сущности Report рефлексивные отношения отсутствуют.

Почему в одном случае происходит ошибка, а в другом нет? Каким образом я могу получить множество subParagraphs с использованием графа сущностей?

P.S.: Я знаю, что могу сделать в Paragraph поле Paragraph parent, и так действительно будет проще, но я хотел бы обойтись иным способом.


Ответы (1 шт):

Автор решения: Zhenyria

Причина ошибки по-видимому заключалась вовсе не в использовании JOIN, а в том, что в выборке брался не владелец отношения, а связанные с ним сущности, в то время как @EntityGraph пытается загрузить граф именно владельца отношения.

В методе getAllSubParagraphs я пробовал разные вариации запросов, но все они давали ошибку:

org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list

Я решил зайти с другой стороны и попробовал сделать такой запрос, в котором выбираться будет именно владелец ассоциации, а не его дочерние сущности:

@EntityGraph(value = "paragraph-graph-questions")
@Query("""
        SELECT p FROM Paragraph p 
        WHERE p.id IN 
        (SELECT sp.id FROM Paragraph p JOIN p.subParagraphs sp WHERE p.id=:id)
        """)
Set<Paragraph> getAllSubParagraphs(int id);

Это действительно сработало. Hibernate формирует такой запрос:

select
    paragraph0_.id as id1_5_0_,
    questions4_.id as id1_7_1_,
    paragraph0_.name as name2_5_0_,
    paragraph0_.grade as grade3_5_0_,
    paragraph0_.is_active as is_activ4_5_0_,
    paragraph0_.num_of_questions_for_paragraph_test as num_of_q5_5_0_,
    paragraph0_.theme_id as theme_id6_5_0_,
    questions4_.answer as answer2_7_1_,
    questions4_.bottom_text as bottom_t3_7_1_,
    questions4_.displaying_setting as displayi4_7_1_,
    questions4_.image as image5_7_1_,
    questions4_.paragraph_id as paragrap8_7_1_,
    questions4_.top_text as top_text6_7_1_,
    questions4_.type as type7_7_1_,
    questions4_.paragraph_id as paragrap8_7_0__,
    questions4_.id as id1_7_0__ 
from
    paragraphs paragraph0_ 
left outer join
    questions questions4_ 
        on paragraph0_.id=questions4_.paragraph_id 
where
    paragraph0_.id in (
        select
            paragraph3_.id 
        from
            paragraphs paragraph1_ 
        inner join
            sub_paragraphs subparagra2_ 
                on paragraph1_.id=subparagra2_.paragraph_id 
        inner join
            paragraphs paragraph3_ 
                on subparagra2_.sub_paragraph_id=paragraph3_.id 
        where
            paragraph1_.id=? 
    )

И в результате загружается именно такой граф, какой я и хотел.

Вероятно, такой запрос не является оптимальным. Здесь делается лишний SELECT. Лучше всего было бы сделать в сущности Paragraph поле Paragraph parent, тогда запрос был бы проще:

SELECT p FROM Paragraph p WHERE p.parent.id=:id

Так что, если вы собираетесь загружать сущность графом, выгружайте владельца ассоциации, потому что загрузка графа будет именно у него.

→ Ссылка