๊ฐœ๋ฐœ์ž HOON
๐Ÿ› HOON DEVLog
๊ฐœ๋ฐœ์ž HOON
์ „์ฒด ๋ฐฉ๋ฌธ์ž
์˜ค๋Š˜
์–ด์ œ
  • ๐Ÿ˜Ž ์ „์ฒด ์นดํ…Œ๊ณ ๋ฆฌ (137)
    • ๐Ÿ“ ์‹ ์ž… ์ธํ„ฐ๋ทฐ ์ค€๋น„ (7)
    • ๐Ÿฆ” ์ทจ์—…์ค€๋น„ ๊ธฐ๋ก (7)
    • โ˜• ์ž๋ฐ” : JAVA (5)
    • ๐Ÿ ์ฝ”๋”ฉํ…Œ์ŠคํŠธ ๋Œ€๋น„ : PS (80)
    • ๐ŸŒฑ ๋ฐฑ์—”๋“œ : Backend (13)
    • ๐Ÿงช ์ปดํ“จํ„ฐ๊ณผํ•™ : CS (11)
    • ๐Ÿ—‚ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค : DB (1)
    • ๐Ÿƒ‍โ™‚๏ธ DEVLOG (8)
    • โš™๏ธ Trouble Shooting (5)

๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

  • ํ™ˆ
  • GitHub
  • Resume

๊ณต์ง€์‚ฌํ•ญ

์ธ๊ธฐ ๊ธ€

์ตœ๊ทผ ๊ธ€

ํ‹ฐ์Šคํ† ๋ฆฌ

hELLO ยท Designed By ์ •์ƒ์šฐ.
๊ฐœ๋ฐœ์ž HOON

๐Ÿ› HOON DEVLog

[QueryDSL] QueryDSL-JPA : JOIN ํ•˜๊ธฐ
๐ŸŒฑ ๋ฐฑ์—”๋“œ : Backend

[QueryDSL] QueryDSL-JPA : JOIN ํ•˜๊ธฐ

2024. 4. 25. 02:05

๐Ÿ ํ™˜๊ฒฝ

- QueryDSL 5.0.0
- Spring Data JPA 3.1.2

 

 

๐Ÿ’œ JOIN ์ข…๋ฅ˜

1. INNER JOIN

JOIN ๋‹นํ•˜๋Š” ํ…Œ์ด๋ธ”์„ A, JOIN์„ ๊ฑฐ๋Š” ํ…Œ์ด๋ธ”์„ B๋ผ๊ณ  ํ•  ๋•Œ, A์™€ B ํ…Œ์ด๋ธ” ๋ชจ๋‘ JOIN์„ ํ•˜๋ ค๋Š” ๋Œ€์ƒ Key ์ปฌ๋Ÿผ์— ๋Œ€ํ•ด ๊ฐ’์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ๋งŒ JOIN์„ ํ•˜๋Š” ๊ฒƒ์„ INNER JOIN์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

queryDSL์—์„œ Inner join์„ ์“ฐ๋Š” ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

queryFactory์—์„œ select์™€ from์ ˆ ์ดํ›„์— join ๋ฉ”์„œ๋“œ๋ฅผ ํฌํ•จํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์•„๋ž˜๋Š” ์ฝ”๋“œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. 

@Entity
@Getter
@Table(name = "neo_starpage")
public class StarPage {

    @EmbeddedId
    @AttributeOverride(name = "value", column = @Column(name = "star_page_id"))
    private StarPageId starPageId;

    // ์Šคํƒ€ ํŽ˜์ด์ง€ ์ •๋ณด
    @Embedded
    private StarPageInfo information;

    // ์Šคํƒ€ ํŽ˜์ด์ง€ ์ˆ˜์ • ๊ฐ€๋Šฅ ์–ด๋“œ๋ฏผ
    @ElementCollection
    @CollectionTable(name = "neo_starpage_admin", joinColumns = @JoinColumn(name = "star_page_id"))
    private Set<NEOMember> admins = new HashSet<>();

    // ์Šคํƒ€ํŽ˜์ด์ง€๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ์š”์†Œ
    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)
    @JoinColumn(name = "star_page_id")
    @OrderColumn(name = "layout_order")
    private List<StarPageLayoutLine> layoutLines = new ArrayList<>();
    
}
@Getter
@Entity
@Table(name = "neo_starpage_layout")
@DiscriminatorColumn(name = "unique_or_categorical")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class StarPageLayoutLine {

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

    @Embedded
    @AttributeOverride(name = "value", column = @Column(name = "layout_title"))
    private LayoutTitle layoutTitle;

    @Column(name = "layout_type")
    @Enumerated(EnumType.STRING)
    private StarPageLayoutType type;

    public StarPageLayoutLine(LayoutTitle layoutTitle, StarPageLayoutType type){
        this.layoutTitle = layoutTitle;
        this.type = type;
    }

    public abstract boolean isRemoveAble();

    public abstract List<? extends StarPageHeadLine> getHeadLineByLayout(StarPageId starPageId, StarPageRepository repo);

}
    @Override
    public Optional<StarPage> findStarPageWithLayout(StarPageId starPageId) {
        return Optional.ofNullable(queryFactory
                .selectFrom(starPage)
                .innerJoin(starPage.layoutLines, starPageLayoutLine).fetchJoin()
                .where(starPage.starPageId.eq(starPageId))
                .fetchOne());
    }

 

์œ„ queryDSL ์ฝ”๋“œ์˜ starPage์™€ starPageLayoutLine์€ Qํด๋ž˜์Šค ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

inner join์„ ํ†ตํ•ด ์Šคํƒ€ํŽ˜์ด์ง€์™€ ๋ ˆ์ด์•„์›ƒ ํ…Œ์ด๋ธ”์„ INNER JOINํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

INNER JOIN์ด๊ธฐ ๋–„๋ฌธ์— ๋ ˆ์ด์•„์›ƒ ํ…Œ์ด๋ธ”์— ํŠน์ • ์Šคํƒ€ํŽ˜์ด์ง€ ์•„์ด๋””๊ฐ€ ์—†๋‹ค๋ฉด, ํ•ด๋‹น ์Šคํƒ€ํŽ˜์ด์ง€ ์•„์ด๋””์— ๋Œ€ํ•œ ์Šคํƒ€ํŽ˜์ด์ง€ ํ…Œ์ด๋ธ”์˜ ๋‚ด์šฉ๋„ ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

 

2. LEFT JOIN, RIGHT JOIN

 

 

Left Join์€ JOIN ๋‹นํ•˜๋Š” ํ…Œ์ด๋ธ” (from ์ ˆ)์˜ ๋ชจ๋“  ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด JOIN ํ•˜๋ ค๋Š” ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ฉ์น˜๋Š” JOIN์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ Right Table์— Left Table์˜ ํ‚ค ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋ฉด, JOIN๋œ ํ…Œ์ด๋ธ”์—์„œ๋Š” ํ•ด๋‹น ๋ถ€๋ถ„์€ NULL์ธ ์ƒํƒœ๋กœ JOIN ๋ฉ๋‹ˆ๋‹ค.

 

 

 

Right Join์€ JOIN ํ•˜๋ ค๋Š” ๋Œ€์ƒ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด JOIN ๋‹นํ•˜๋Š” ๋Œ€์ƒ ํ…Œ์ด๋ธ”(from)์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ฉ์น˜๋Š” JOIN์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ Left Table์— Right Table์˜ ํ‚ค ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋ฉด, JOIN๋œ ํ…Œ์ด๋ธ”์—์„œ๋Š” ํ•ด๋‹น ๋ถ€๋ถ„์€ NULL์ธ ์ƒํƒœ๋กœ JOIN ๋ฉ๋‹ˆ๋‹ค.

 

 

QueryDSL์—์„œ๋Š” .leftJoin(), .rightJoin()์„ ํ†ตํ•ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” .leftJoin()์„ ์‚ฌ์šฉํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

 

    @Override
    public List<RepresentativeArticleHeadLine> searchHeadLineByStarPageIdAndLimit(StarPageId id, long limit) {
        // ์นดํ…Œ๊ณ ๋ฆฌ๋ช…, ์ œ๋ชฉ, ์ž‘์„ฑ์ž, ์ข‹์•„์š” ์ˆ˜, ๊ธ€ ๋‚ด์šฉ ํ™•์ธ API Path, ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ DTO์— ๋‹ด๊ธฐ.
        return queryFactory
                .select(
                        Projections.constructor(
                                RepresentativeArticleHeadLine.class,
                                starPagePost.id, starPagePost.likeCount, starPagePost.author.authorName,
                                category.categoryInformation.categoryTitle, starPagePost.postType.stringValue(), starPagePost.title,
                                albumPost.image.path.coalesce(commonPost.representativeImage).as("representativeImage"),
                                Expressions.asString("๋ฒ ์ŠคํŠธ ํŒฌ ๊ฒŒ์‹œ๋ฌผ").as("tapName")
                        ))
                .from(starPagePost)
                .innerJoin(category).on(starPagePost.categoryId.eq(category.categoryId))
                .leftJoin(albumPost).on(eqPostId(albumPost.id))
                .leftJoin(commonPost).on(eqPostId(commonPost.id))
                .leftJoin(starPage).on(starPage.starPageId.eq(category.starPageId))
                .where(
                        eqStarPageId(id),
                        neHostEmail(starPage.information.host.email),
                        eqPostStatus(PostStatus.MAIN_EXPOSED)
                )
                .orderBy(starPagePost.exposureAt.desc())
                .limit(limit)
                .fetch();
    }

starPagePost๋Š” common, album, vote, goldbalance ์ด 4๊ฐœ์˜ ์œ ํ˜•์œผ๋กœ ๋‚˜๋ˆ ์ ธ์žˆ๊ณ , JOIN ํ…Œ์ด๋ธ” ์ „๋žต์œผ๋กœ ์ƒ์†ํ•˜๋Š” ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ์ž…๋‹ˆ๋‹ค.

์Šคํƒ€ํŽ˜์ด์ง€ ํฌ์ŠคํŠธ๋“ค ์ค‘์— album, common๋งŒ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‚˜๋จธ์ง€ ํฌ์ŠคํŠธ ์œ ํ˜•์— ๋Œ€ํ•ด albumPost, commonPost๋ฅผ innerJoin์„ ํ•œ๋‹ค๋ฉด vote, goldbalance์ธ ํฌ์ŠคํŠธ๋“ค์€ ํ…Œ์ด๋ธ”์—์„œ ๋น ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— leftJoin์„ ํ™œ์šฉํ•œ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.

 

 

3. Fetch JOIN

 

JPQL์˜ Fetch JOIN์€ ๊ด€๊ณ„ํ˜• ์—”ํ‹ฐํ‹ฐ์—์„œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋“ค์„ ํ•จ๊ป˜ ๋กœ๋”ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ง€์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. Fetch JOIN์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉ(Lazy Loading)์ด ์•„๋‹ˆ๋ผ ์ฆ‰์‹œ ๋กœ๋”ฉ(Eager Loading)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด N+1 ์ฟผ๋ฆฌ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

JPA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” Entity, ElementCollection์— ๋Œ€ํ•ด JOIN์„ ํ†ตํ•ด ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๊ธธ ์›ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์€๋ฐ Fetch JOIN์„ ํ†ตํ•ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

QueryDSL-JPA๋Š” JPQL์„ Typesafeํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋ฏ€๋กœ, QueryDSL-JPA์—์„œ๋„ FetchJoin์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•์€ ๋‹จ์ˆœํ•˜๊ฒŒ .innerJoin(), .leftJoin(), .rightJoin() ๋’ค์— .fetchJoin()์„ ๋ถ™์—ฌ์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

.innerJoin(starPage.layoutLines, starPageLayoutLine).fetchJoin()

 

์•„๋ž˜๋Š” FetchJoin์„ ํ•œ ๊ฒฝ์šฐ์™€ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

fetchJoin()์„ ํ•œ ๊ฒฝ์šฐ

 

fetchJoin()์„ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ

 

- FetchJoin์„ ํ•œ ๊ฒฝ์šฐ์—๋Š” ์—ฐ๊ด€๊ด€๊ณ„์— ์†ํ•œ ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐ’๊นŒ์ง€ ํ•œ ๋ฒˆ์— SELECTํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ํ†ตํ•ด layout์„ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

- FetchJoin์„ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” JOIN์€ ์ž˜ ๋˜์—ˆ์ง€๋งŒ, SELECT์—๋Š” layout์— ๋Œ€ํ•œ(์—ฐ๊ด€๊ด€๊ณ„ ์—”ํ‹ฐํ‹ฐ) ์ •๋ณด๋Š” ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” ์Šคํƒ€ํŽ˜์ด์ง€์—์„œ ๋ ˆ์ด์•„์›ƒ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์ฐธ๊ณ ๋กœ ์กฐํšŒ๋ฅผ ์œ„ํ•ด ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹Œ 'DTO'๋ฅผ SELECT ํ•œ๋‹ค๋ฉด, FetchJoin์€ ๋‹น์—ฐํ•˜๊ฒŒ๋„ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

FetchJoin์€ JPQL์˜ Fetch JOIN์€ ๊ด€๊ณ„ํ˜• ์—”ํ‹ฐํ‹ฐ์—์„œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋“ค์„ ํ•จ๊ป˜ ๋กœ๋”ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ง€์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ DTO๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค.

query specified join fetching, but the owner of the fetched association was not present in the select list

 

DTO๋Š” ์ผ๋ฐ˜์ ์ธ JOIN๋งŒ ์ง„ํ–‰ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

+ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ JOIN์„ ํ•˜๋ฉด ๋ฌด์กฐ๊ฑด ์ฆ‰์‹œ๋กœ๋”ฉ์„ ์œ„ํ•œ JOIN์ธ๊ฐ€? ๋ฌด์กฐ๊ฑด FetchJoin์ธ๊ฐ€?

์ฆ‰์‹œ๋กœ๋”ฉ์„ ์œ„ํ•œ JOIN์ด๋ผ๋ฉด FetchJoin์„ ์“ฐ๋ฉด ๋˜์ง€๋งŒ,
์ฆ‰์‹œ๋กœ๋”ฉ์„ ์›ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ '์กฐ๊ฑด'๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” ์ผ๋ฐ˜ ์กฐ์ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

4. CROSS JOIN (๋ช…์‹œ์  JOIN์„ ํ†ตํ•ด CROSS JOIN์— ์˜ํ•œ ์„ฑ๋Šฅ ์ €ํ•˜ ์ œ๊ฑฐ)

 

Querydsl (JPA) ์—์„œ Cross Join ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ

JPA ๊ธฐ๋ฐ˜์˜ ํ™˜๊ฒฝ์—์„œ Querydsl๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋ฉด @OneToOne ๊ด€๊ณ„์—์„œ Join ์ฟผ๋ฆฌ ์ž‘์„ฑ์‹œ ์ฃผ์˜ํ•˜์ง€ ์•Š์œผ๋ฉด Cross Join์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. CrossJoin ์ด๋ž€ ์ง‘ํ•ฉ์—์„œ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒฝ์šฐ๋ฅผ ์ด์•ผ๊ธฐ ํ•ฉ๋‹ˆ๋‹ค.

jojoldu.tistory.com

 

QueryDSL, JPA CrossJoin

๊ธ€์„ ์ ๊ธฐ ์•ž์„œ์„œ ๋ช‡ ๊ธ€์ž๋ฅผ ์ ์–ด๋ณด๋ ค ํ•œ๋‹ค.์ด๋ฒˆ์—๋Š”... ์‚ฌ์ง„๋„ ๋„ฃ๊ณ  ์ •์„ฑ๊ป ์ ์–ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค...์ผ๋‹จ, ์ฒซ๋ฒˆ์งธ๋กœ ์œ ๋ช…ํ•˜์‹  ๊ฐœ๋ฐœ์ž 'ํ–ฅ๋กœ'๋‹˜์˜ ๋ธ”๋กœ๊ทธ์˜ ๊ธ€์„ ์ฐธ๊ณ ํ•˜์˜€๋‹ค.๊ทธ๋ฆฌ๊ณ  ๋‘๋ฒˆ์งธ๋กœ ์ฐธ๊ณ  ๋ฟ

velog.io

 

๋ช…์‹œ์ ์ธ JOIN์„ ์‚ฌ์šฉํ•˜๋ฉด CROSS JOIN์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ์€ ์—”ํ‹ฐํ‹ฐ์—์„œ ๋ณ„๋‹ค๋ฅธ JOIN ์—†์ด WHERE ์กฐ๊ฑด ์ ˆ์—์„œ ์—ฐ๊ด€๊ด€๊ณ„ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ํ•˜์ด๋ฒ„๋„ค์ดํŠธ์—์„œ CROSS JOIN์„ ์•”๋ฌต์ ์œผ๋กœ ์„ ํƒํ•˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋”ฐ๋ผ์„œ QueryDSL์„ ์ž‘์„ฑํ•  ๋•Œ ๋ช…์‹œ์ ์œผ๋กœ JOIN๋ฌธ์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

 

 

๐Ÿ’œ JOIN ๋Œ€์ƒ์— ๋”ฐ๋ฅธ ์ •๋ฆฌ

1. ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” Entity๊ฐ„ JOINํ•˜๊ธฐ

 

๐Ÿค” StarPage Entity

@Entity
@Getter
@Table(name = "neo_starpage")
public class StarPage {

    @EmbeddedId
    @AttributeOverride(name = "value", column = @Column(name = "star_page_id"))
    private StarPageId starPageId;

    // ์Šคํƒ€ ํŽ˜์ด์ง€ ์ •๋ณด
    @Embedded
    private StarPageInfo information;

    // ์Šคํƒ€ ํŽ˜์ด์ง€ ์ˆ˜์ • ๊ฐ€๋Šฅ ์–ด๋“œ๋ฏผ
    @ElementCollection
    @CollectionTable(name = "neo_starpage_admin", joinColumns = @JoinColumn(name = "star_page_id"))
    private Set<NEOMember> admins = new HashSet<>();

    // ์Šคํƒ€ํŽ˜์ด์ง€๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ์š”์†Œ
    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)
    @JoinColumn(name = "star_page_id")
    @OrderColumn(name = "layout_order")
    private List<StarPageLayoutLine> layoutLines = new ArrayList<>();
    
}

 

์œ„์˜ ์ฝ”๋“œ์—์„œ, StarPage์™€ StarPageLayoutLine์€ 1:N์˜ ์—ฐ๊ด€๊ด€๊ณ„(@OneToMany)๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์ด ๋‘ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•œ ๋ฒˆ์— InnerJoinํ•ด ๊ฐ€์ ธ์˜ค๋ ค๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

 

    @Override
    public Optional<StarPage> findStarPageWithLayout(StarPageId starPageId) {
        return Optional.ofNullable(queryFactory
                .selectFrom(starPage)
                .innerJoin(starPage.layoutLines, starPageLayoutLine).fetchJoin()
                .where(starPage.starPageId.eq(starPageId))
                .fetchOne());
    }
// .innerJoin(์—”ํ‹ฐํ‹ฐ์˜ ์—ฐ๊ด€๊ด€๊ณ„ ํ•„๋“œ, ์—ฐ๊ด€๊ด€๊ณ„ ๋Œ€์ƒ Qํด๋ž˜์Šค).fetchJoin()
.innerJoin(starPage.layoutLines, starPageLayoutLine).fetchJoin()

์œ„์™€ ๊ฐ™์€ ๋ฌธ๋ฒ•์œผ๋กœ Join์„ ํ•œ๋‹ค๋ฉด, JoinColumn์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‘ ํ…Œ์ด๋ธ”์„ Joinํ•ด ์Šคํƒ€ํŽ˜์ด์ง€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์–ธ๊ธ‰ํ•œ FetchJoin์„ ์‚ฌ์šฉํ•ด ๋ ˆ์ด์•„์›ƒ๋„ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์ฒ˜๋Ÿผ์š”.

 

 

 

2. ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š” Entity๊ฐ„ JOINํ•˜๊ธฐ (๋‹ค๋ฅธ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์˜ ID๋ฅผ ์†Œ์ง€ํ•œ ๊ฒฝ์šฐ ๋“ฑ.)

 

ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๊ฐ€์ง„ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด์„œ๋งŒ JOIN์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

DDD START!๋ผ๋Š” ์ฑ…์—์„œ๋Š” ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์˜ ์—ฐ๊ด€๊ด€๊ณ„ ๋‚จ์šฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ƒ๋Œ€ ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ์— ๋Œ€ํ•œ ID๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฉ์‹์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์—๋Š” JPA ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ์ƒ๋Œ€ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ID๋งŒ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์œ„์™€ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ๋Š” ํ…Œ์ด๋ธ” ์กฐ์ธ์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

 

QueryDSL์„ ์‚ฌ์šฉํ•˜๋ฉด .on ๋ฉ”์„œ๋“œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” ์˜ˆ์‹œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

 

๐Ÿค” StarPagePost Entity

@Entity
@Table(name = "star_page_post")
@Inheritance(strategy = InheritanceType.JOINED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class StarPagePost extends NEOTimeDefaultEntity {

    @Id
    @Column(name = "post_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Embedded
    @Column(name = "category_id")
    private CategoryId categoryId;

    @Column(name = "post_title")
    private String title;

    @Embedded
    @Column(name = "author_name")
    private Author author;

    @Column(name = "status")
    @Convert(converter = PostStatusConverter.class)
    private PostStatus status;

    @Column(name = "like_count", nullable = false)
    private int likeCount;

    @Column(name = "host_heart", nullable = false)
    private boolean hostHeart;

    @Column(name = "exposure_at")
    private LocalDateTime exposureAt;

    @Column(name = "post_type", nullable = false)
    @Convert(converter = PostTypeConverter.class)
    private PostType postType;

    @ElementCollection
    @CollectionTable(name = "star_page_post_like", joinColumns = @JoinColumn(name = "post_id"))
    private Set<PostLike> likes = new HashSet<>();
 
 }

 

๐Ÿค” Category Entity

@Entity
@Table(name = "neo_starpage_category")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Category {

    @Getter
    @EmbeddedId
    @Column(name = "category_id")
    private CategoryId categoryId;

    @Getter
    @Column(name = "starpage_id")
    private StarPageId starPageId;

    @Column(name = "category_status")
    @Convert(converter = CategoryStatusConverter.class)
    private CategoryStatus categoryStatus;

    @Getter
    @Column(name = "content_type")
    @Convert(converter = ContentTypeConverter.class)
    private ContentType contentType;

    @Getter
    @Embedded
    @AttributeOverride(name = "categoryTitle", column = @Column(name = "title"))
    private CategoryInformation categoryInformation;

    @Embedded
    private ContentRestriction restriction;
    
}

 

StarPagePost ์—”ํ‹ฐํ‹ฐ๋Š” Category์˜ ID๋ฅผ ์†Œ์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. JPA ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค๋ฉด, Category : StarPagePost๋Š” 1:N์˜ ๊ด€๊ณ„๋ฅผ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์•„๋ž˜๋Š” ๋ฉ”์ธํ™”๋ฉด์— ๋…ธ์ถœ๋  ํ—ค๋“œ๋ผ์ธ์„ ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋ฝ‘์•„์˜ค๋Š” QueryDSL ์ฝ”๋“œ์ธ๋ฐ์š”,

๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” ๋ณด์ง€ ์•Š๊ณ  ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š” Entity๊ฐ„ JOINํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๊ธฐ ์œ„ํ•œ ์ž๋ฃŒ์ด๋‹ˆ innerJoin๊ณผ leftJoin๋งŒ ๋ณด๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

๐Ÿค” ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š” Entity๊ฐ„ Joinํ•˜๋Š” QueryDSL ์˜ˆ์‹œ ์ฝ”๋“œ

    @Override
    public List<RepresentativeArticleHeadLine> searchHeadLineByStarPageIdAndLimit(StarPageId id, long limit) {
        // ์นดํ…Œ๊ณ ๋ฆฌ๋ช…, ์ œ๋ชฉ, ์ž‘์„ฑ์ž, ์ข‹์•„์š” ์ˆ˜, ๊ธ€ ๋‚ด์šฉ ํ™•์ธ API Path, ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ DTO์— ๋‹ด๊ธฐ.
        return queryFactory
                .select(
                        Projections.constructor(
                                RepresentativeArticleHeadLine.class,
                                starPagePost.id, starPagePost.likeCount, starPagePost.author.authorName,
                                category.categoryInformation.categoryTitle, starPagePost.postType.stringValue(), starPagePost.title,
                                albumPost.image.path.coalesce(commonPost.representativeImage).as("representativeImage"),
                                Expressions.asString("๋ฒ ์ŠคํŠธ ํŒฌ ๊ฒŒ์‹œ๋ฌผ").as("tapName")
                        ))
                .from(starPagePost)
                .innerJoin(category).on(starPagePost.categoryId.eq(category.categoryId))
                .leftJoin(albumPost).on(eqPostId(albumPost.id))
                .leftJoin(commonPost).on(eqPostId(commonPost.id))
                .leftJoin(starPage).on(starPage.starPageId.eq(category.starPageId))
                .where(
                        eqStarPageId(id),
                        neHostEmail(starPage.information.host.email),
                        eqPostStatus(PostStatus.MAIN_EXPOSED)
                )
                .orderBy(starPagePost.exposureAt.desc())
                .limit(limit)
                .fetch();
    }
// .innerJoin(Joinํ•˜๊ณ ์ž ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ Q๊ฐ์ฒด).on(์กฐ๊ฑด(์†Œ์ง€ํ•˜๊ณ  ์žˆ๋Š” ์•„์ด๋””์™€, Joinํ•˜๊ณ ์ž ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ์˜ ์•„์ด๋””๊ฐ€ ๋™์ผํ•˜๋‹ค๋Š” ์กฐ๊ฑด))
.innerJoin(category).on(starPagePost.categoryId.eq(category.categoryId))

 

์œ„์™€ ๊ฐ™์ด Join๊ณผ on ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์—†๋Š” ๋‘ ์—”ํ‹ฐํ‹ฐ๊ฐ„ Join๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

 

 

3. Entity์™€ ์ปฌ๋ ‰์…˜ VO(Element Collection)๊ฐ„ JOINํ•˜๊ธฐ

 

[QueryDSL] QueryDSL-JPA : @ElementCollection VO์— ๋Œ€ํ•œ JOIN

๐Ÿค” ์‚ฌ์šฉ๋œ ๋„๋ฉ”์ธ ์ฝ”๋“œ ๐Ÿ’œ StarPage Aggregate Root @Entity @Getter @Table(name = "neo_starpage") public class StarPage { @EmbeddedId @AttributeOverride(name = "value", column = @Column(name = "star_page_id")) private StarPageId starPageId; @Embed

hoons-dev.tistory.com

 

์ด์ „ ๊ธ€์—์„œ ์ž‘์„ฑํ–ˆ๋“ฏ์ด, ๋ณ„๋‹ค๋ฅธ .on() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ํ•ด๋‹น Qํƒ€์ž… ๊ฐ์ฒด์—์„œ ์ปฌ๋ ‰์…˜ VO๋ฅผ ์„ ํƒํ•˜๋ฉด JOIN์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    @Override
    public Optional<StarPage> findStarPageWithInformation(StarPageId starPageId) {
        return Optional.ofNullable(queryFactory.
                selectFrom(starPage)
                .innerJoin(starPage.information.host.starTypes).fetchJoin()
                .innerJoin(starPage.information.host.snsLines).fetchJoin()
                .where(starPage.starPageId.eq(starPageId))
                .fetchOne());
    }

 

์œ„์˜ ์ฝ”๋“œ์—์„œ starPage๋Š” ์• ๊ทธ๋ฆฌ๊ฑฐํŠธ ๋ฃจํŠธ์ธ ์—”ํ‹ฐํ‹ฐ์ด๋ฉฐ, starPage.information.host.starTypes๋Š” ์ปฌ๋ ‰์…˜ ๊ฐ’ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ๋ณ„๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์—†์ด Qํƒ€์ž…์˜ ์ปฌ๋ ‰์…˜ ๊ฐ’ ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ์ž๋™์œผ๋กœ ์—ฐ๊ณ„๋œ ์—”ํ‹ฐํ‹ฐ์˜ ID๋ฅผ ๊ธฐ์ค€์œผ๋กœ JOIN๋˜์–ด SELECTํ•˜๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

์ €์ž‘์žํ‘œ์‹œ ๋น„์˜๋ฆฌ ๋ณ€๊ฒฝ๊ธˆ์ง€ (์ƒˆ์ฐฝ์—ด๋ฆผ)

'๐ŸŒฑ ๋ฐฑ์—”๋“œ : Backend' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Redis] M1 MacOS์—์„œ Docker Redis Cluster ์„ค์ •ํ•˜๊ธฐ + SpringBoot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํด๋Ÿฌ์Šคํ„ฐ ์—ฐ๊ฒฐํ•˜๊ธฐ  (1) 2024.05.11
[QueryDSL] QueryDSL-JPA : DTO Projection๊ณผ Transform  (1) 2024.04.26
[QueryDSL] QueryDSL-JPA : @ElementCollection VO์— ๋Œ€ํ•œ JOIN  (1) 2024.04.23
[Spring] Spring Security + OAuth2.0 + JWT Token์„ ํ™œ์šฉํ•œ ์†Œ์…œ๋กœ๊ทธ์ธ ์ด์ •๋ฆฌ - (1) ๋ฐฐ๊ฒฝ ์ง€์‹ ์ดํ•ดํ•˜๊ธฐ, ์ „์ฒด ๊ทธ๋ฆผ ์‚ดํŽด๋ณด๊ธฐ  (0) 2023.09.22
[Spring] API ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ Swagger ๋„์ž…ํ•˜๊ธฐ (gradle ๊ธฐ์ค€)  (0) 2023.03.24
    '๐ŸŒฑ ๋ฐฑ์—”๋“œ : Backend' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
    • [Redis] M1 MacOS์—์„œ Docker Redis Cluster ์„ค์ •ํ•˜๊ธฐ + SpringBoot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํด๋Ÿฌ์Šคํ„ฐ ์—ฐ๊ฒฐํ•˜๊ธฐ
    • [QueryDSL] QueryDSL-JPA : DTO Projection๊ณผ Transform
    • [QueryDSL] QueryDSL-JPA : @ElementCollection VO์— ๋Œ€ํ•œ JOIN
    • [Spring] Spring Security + OAuth2.0 + JWT Token์„ ํ™œ์šฉํ•œ ์†Œ์…œ๋กœ๊ทธ์ธ ์ด์ •๋ฆฌ - (1) ๋ฐฐ๊ฒฝ ์ง€์‹ ์ดํ•ดํ•˜๊ธฐ, ์ „์ฒด ๊ทธ๋ฆผ ์‚ดํŽด๋ณด๊ธฐ
    ๊ฐœ๋ฐœ์ž HOON
    ๊ฐœ๋ฐœ์ž HOON
    ์ข‹์€ ๋ฐฑ์—”๋“œ ์—”์ง€๋‹ˆ์–ด๊ฐ€ ๋˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ก์„ ๋ชจ์•˜์Šต๋‹ˆ๋‹ค. # ์ฃผ๋‹ˆ์–ด # ๋ฐฑ์—”๋“œ # ๊ฐœ๋ฐœ์ž

    ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”