인프런 커뮤니티 질문&답변

김김님의 프로필 이미지
김김

작성한 질문수

실전! Querydsl

JOIN 2번 질문입니다..! (+is not a root path 에러)

작성

·

2.1K

·

수정됨

0

개인 프로젝트중인데 도저히 해결을 못 하겠어서 질문 드립니다..

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Cafe {

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

    @NotNull
    @Column(unique = true)
    private String name;

    private String phoneNumber;
    private String bhours;
    private String address;
    private String domain;
    private String location;

    @OneToMany(mappedBy = "cafe")
    private List<Theme> themes = new ArrayList<>();

}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Theme {

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "cafe_id")
    private Cafe cafe;

    private String name;
    private String genre;
    private String activity;
    private Integer difficult;
    private Integer limitTime;
    private Integer recommendStart;
    private Integer recommendEnd;

    @Column(length = 500)
    private String info;

    private String imageUrl;

    @OneToMany(mappedBy = "theme")
    private List<Review> reviews = new ArrayList<>();


    //==연관관계 편의 메서드==//
    public void changeCafe(Cafe cafe) {
        this.cafe = cafe;
        cafe.getThemes().add(this);
    }
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
public class Review {

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "theme_id")
    private Theme theme;

    private String nickname;
    private String password;
    private LocalDateTime playdate;
    private Double difficult;
    private String clear;
    private String time;
    private Integer hint;

    @Column(length = 700)
    private String content;

    @CreatedDate
    private LocalDateTime indate;

    private Double rating;
}

 

Entity는 위와 같고

조회하려는 DTO는 다음과 같습니다.

public class ThemeListDTO {

    private Long themeId;
    private String themeName;
    private String genre;
    private Integer recommendStart;
    private Integer recommendEnd;
    private String imageUrl;

    private String cafeName;
    private String location;

    private Double ratingAvg;

    @QueryProjection
    public ThemeListDTO(Long themeId, String themeName, String genre,
                        Integer recommendStart, Integer recommendEnd, String imageUrl,
                        String cafeName, String location, Double ratingAvg) {
        this.themeId = themeId;
        this.themeName = themeName;
        this.genre = genre;
        this.recommendStart = recommendStart;
        this.recommendEnd = recommendEnd;
        this.imageUrl = imageUrl;
        this.cafeName = cafeName;
        this.location = location;
        this.ratingAvg = ratingAvg;
    }
}

 

theme와 cafe를 join하여 해당 테마의 카페 정보를 가져오고,

theme와 review를 join하여 해당 테마에 남겨진 리뷰들의 평균 rating 값을 가져오고 싶습니다.

또한 리뷰점수로 desc 하여 테마 리스트를 정렬하고 싶습니다.

 

이를 위해서

public class ThemeRepositoryCustomImpl implements ThemeRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    public ThemeRepositoryCustomImpl(JPAQueryFactory queryFactory) {
        this.queryFactory = queryFactory;
    }

    @Override
    public Page<ThemeListDTO> themeAllListPage(Pageable pageable) {

        List<ThemeListDTO> themeListContent = queryFactory
                .select(new QThemeListDTO(
                        theme.id,
                        theme.name,
                        theme.genre,
                        theme.recommendStart,
                        theme.recommendEnd,
                        theme.imageUrl,
                        cafe.name,
                        cafe.location,
                        review.rating.avg()
                ))
                .from(theme)
                .leftJoin(theme.cafe, cafe)
                .leftJoin(theme, review.theme)
                .orderBy(review.rating.avg().desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        JPAQuery<Long> countQuery = queryFactory
                .select(theme.count())
                .from(theme);

        return PageableExecutionUtils.getPage(themeListContent, pageable, countQuery::fetchOne);
    }
}

위와 같이 코드를 작성했습니다.

 

하지만 이를 실행했을 경우

ERROR 8584 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: review.theme is not a root path; nested exception is java.lang.IllegalArgumentException: review.theme is not a root path] with root cause

java.lang.IllegalArgumentException: review.theme is not a root path

이런 에러 문구가 뜨는데 어떻게 바꾸는 게 맞는 것인가요 ?

해당 에러 문구를 구글링해봐도 답을 잘 모르겠어서 질문 드립니다 ..,,

 

  1. 해당 에러 문구가 무슨 뜻인지, 어떻게 해결하면 좋을지

  2. join 두번 했을 때 이렇게 하는 게 맞는 것인지

  3. 리뷰 점수대로 desc 테마 리스트를 정렬하려고 하는데 orderBy에 저렇게 작성하는것이 맞을지

에 대하여 답변해주시면 정말 감사하겠습니다 ..

답변 2

0

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 기매님

다음 문법이 잘못되었습니다.

.leftJoin(theme, review.theme)

작성하신 다음과 같이 왼쪽에는 조인 대상, 오른쪽에는 alias가 와야합니다.

.leftJoin(theme.cafe, cafe)

조인 부분을 다시 참고해주세요.

감사합니다.

0

안녕하세요, 인프런 AI 인턴이에요.

해당 에러가 뜨는 이유는 review.theme이 root path가 아닌 것으로 판단해서 발생하는 문제인데요. 이를 해결하려면 from 절에서 해당 객체를 다시 선언해주면 됩니다. 즉, querydsl에서는 join을 할 때 alias를 줘서 해당 객체를 다시 선언해주는 방식으로 작성해야 해결할 수 있습니다.

또한, join을 두번 할 때에는 @QueryProjection과 함께 Constructor Expression을 사용하는 것이 좋습니다. orderBy에서는 저렇게 작성해도 상관 없지만, 평균값이 null인 경우도 있을 수 있으니 이에 대한 처리도 함께 해주시면 됩니다.

아래는 해당 내용을 반영한 코드입니다.

public Page<ThemeListDTO> themeAllListPage(Pageable pageable) {

        QTheme theme = QTheme.theme;
        QCafe cafe = QCafe.cafe;
        QReview review = QReview.review;

        List<ThemeListDTO> themeListContent = queryFactory
                .select(new QThemeListDTO(
                        theme.id,
                        theme.name,
                        theme.genre,
                        theme.recommendStart,
                        theme.recommendEnd,
                        theme.imageUrl,
                        cafe.name,
                        cafe.location,
                        review.rating.avg().coalesce(0.0)
                ))
                .from(theme)
                .leftJoin(theme.cafe, cafe)
                .leftJoin(theme.reviews, review)
                .groupBy(theme.id)
                .orderBy(review.rating.avg().desc().nullsLast())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        JPAQuery<Long> countQuery = queryFactory
                .select(theme.count())
                .from(theme);

        return PageableExecutionUtils.getPage(themeListContent, pageable, countQuery::fetchOne);
    }

코드를 수정해보시고 문제가 있으면 댓글로 다시 문의해주세요. 감사합니다.

김김님의 프로필 이미지
김김

작성한 질문수

질문하기