Query Dslを使用して複雑な構造のデータクエリーを処理する

8762 ワード

概要


エッジプロジェクトを行う過程で、いくつかの厄介なデータ構造に関連しています.
データ構造は階層構造であり、A->B->C構造に属する.

AにBがあり、BにCの構造があり、jpa repositoryで問い合わせるとN+1の問題も発生します.

悶える


テーブル構造を根本的に変更できるかどうかを考慮し,継承関係マッピングを考慮したが,親クラスのデータは次のサブクラスでは利用できない.つまり,これは拡張の概念ではないので,この方法を用いないことにした.
そうであれば、既存のデータ構造を維持し、より効率的なクエリーを作成する必要があります.

JPQL vs QueryDsl


JPQLについては、照会はStringで直接記入する必要があるため、煩わしい.
だからコンパイル中にエラーをキャプチャできません...個人的には可読性が非常に悪いと思います
そこでQueryDslを用いて解決することにした.

既存のコード

// JpaRepository에서 호출
repository.findByEmailAndSubjectId(email, id);

結果


Hibernate: 
    /* select
        generatedAlias0 
    from
        UserCheckList as generatedAlias0 
    where
        (
            generatedAlias0.email=:param0 
        ) 
        and (
            generatedAlias0.subjectId=:param1 
        ) */ select
            usercheckl0_.id as id1_6_,
            usercheckl0_.email as email2_6_,
            usercheckl0_.subject_id as subject_3_6_ 
        from
            user_check_list usercheckl0_ 
        where
            usercheckl0_.email=? 
            and usercheckl0_.subject_id=?
Hibernate: 
    select
        usercheckl0_.user_check_list_id as user_che3_8_0_,
        usercheckl0_.id as id1_8_0_,
        usercheckl0_.id as id1_8_1_,
        usercheckl0_.section_title as section_2_8_1_,
        usercheckl0_.user_check_list_id as user_che3_8_1_ 
    from
        user_check_list_section usercheckl0_ 
    where
        usercheckl0_.user_check_list_id=?
Hibernate: 
    select
        elements0_.user_check_list_section_id as user_che4_7_0_,
        elements0_.id as id1_7_0_,
        elements0_.id as id1_7_1_,
        elements0_.element_title as element_2_7_1_,
        elements0_.is_checked as is_check3_7_1_,
        elements0_.user_check_list_section_id as user_che4_7_1_ 
    from
        user_check_list_element elements0_ 
    where
        elements0_.user_check_list_section_id=?
関連関係のfetchTypeはLazyであるため、必要に応じてn回のクエリがさらに失敗するかどうかをチェックすることができます.
これはまだオンラインになっていないサービスですが、問題になる部分には十分です.
QueryDslでこの部分を優雅に処理しましょう.

QueryDsl

fun findByEmailAndSubjectId(email: String, subjectId: Long): UserCheckList? {
        val userChecklist = QUserCheckList.userCheckList
        val section = QUserCheckListSection.userCheckListSection

        val userCheckLists = query.selectFrom(userChecklist)
            .leftJoin(userChecklist.userCheckListSections, section)
            .fetchJoin()
            .leftJoin(section.elements)
            .fetchJoin()
            .where(
                QUserCheckList.userCheckList.email.eq(email)
                    .and(QUserCheckList.userCheckList.subjectId.eq(subjectId))
            )
            .fetch()
            // join을 하게되면 테이블이 합쳐지게 되는데 이 과정에서 중복 데이터가 발생한다.
            // 따라서 직접 중복 제거를 한다.
            .stream()
            .distinct()
            .collect(Collectors.toList())

        return if(userCheckLists.isEmpty()) null else userCheckLists[0]
}

結果

Hibernate: 
*/ select
            usercheckl0_.id as id1_6_0_,
            usercheckl1_.id as id1_8_1_,
            elements2_.id as id1_7_2_,
            usercheckl0_.email as email2_6_0_,
            usercheckl0_.subject_id as subject_3_6_0_,
            usercheckl1_.section_title as section_2_8_1_,
            usercheckl1_.user_check_list_id as user_che3_8_1_,
            usercheckl1_.user_check_list_id as user_che3_8_0__,
            usercheckl1_.id as id1_8_0__,
            elements2_.element_title as element_2_7_2_,
            elements2_.is_checked as is_check3_7_2_,
            elements2_.user_check_list_section_id as user_che4_7_2_,
            elements2_.user_check_list_section_id as user_che4_7_1__,
            elements2_.id as id1_7_1__ 
        from
            user_check_list usercheckl0_ 
        left outer join
            user_check_list_section usercheckl1_ 
                on usercheckl0_.id=usercheckl1_.user_check_list_id 
        left outer join
            user_check_list_element elements2_ 
                on usercheckl1_.id=elements2_.user_check_list_section_id 
        where
            usercheckl0_.email=? 
            and usercheckl0_.subject_id=?
クエリーは一度だけ外に出るのが見えます.
また、これらのデータは一度にインポートするデータであり、インポートするカラムは個別に指定されていません.

ポスト


JPQLの代わりにQueryDslを使ってみましたが、既存のJPQLについては
"select * from data " +
"where id = blah"
上記のように記入するのは大変です.
しかしQueryDslを導入することで,利便性と可読性をもたらすことができる.
QueryDslの処理は未熟ですが、勉強でもっとうまく使えたら、とても良いライブラリになるはずです.

付言


間違ったところを指摘してください