[JPA] 객체 지향 쿼리 언어 - QueryDSL(2)

Lee Seung Jae·2021년 7월 31일
0

이번 포스팅에서는 조인에 대해 알아볼 것이다.

조인

조인은 innerJoin(join), leftJoin, rightJoin, fullJoin을 사용할 수 있고 추가로 JPQL의 on과 성능 최적화를 위한 fetchJoin을 사용할 수 있다.

jpaQueryFactory.selectFrom(item).join(조인할 쿼리클래스).fetch();

연관관계가 있으면 그냥 join만 사용해도 되지만 지금은 on이 추가되어 on() 절로 연관관계 없이 조인도 가능하다.

jpaQueryFactory.selectFrom(item).join(chair).on(chair.name.eq(item.name)).fetch();


from절에 여러 조건을 사용해서 세타조인도 가능하다.

서브 쿼리

서브 쿼리는 예전 버전에서는 JPASubQuery를 사용했지만 업데이트가 되어 현재 버전에서는 JPAExpressions 를 사용하여 서브쿼리를 작성한다.

@Test
@DisplayName("subQuery 테스트")
void subQueryTest() {
    jpaQueryFactory.selectFrom(item)
                   .where(item.name.eq(String.valueOf(JPAExpressions.selectFrom(chair).where(chair.name.eq("item3")))))
                   .fetch();
}

전 직장에서 근무하였을때 서브쿼리로 너무 많은것들을 처리했던 레거시 쿼리가 상당히 많아서
서브쿼리를 사용할때는 항상 생각해보고 쿼리를 짜야 좋을것 같다.
이게 다른 조회를 여러번 하는것이나 Join을 사용할때 보다 느린것 같다. 자세하게 속도를 비교해보면서 포스팅을 한번 했었어야 했는데 이게 안되서 개인적으로는 아쉬운 부분이다.😅

프로젝션

프로젝션 대상이 하나라면 해당하는 타입으로 결과를 받아야 한다.

List<String> list = jpaQueryFactory.select(item.name).from(item).orderBy(item.name.desc()).fetch();

그렇지만 대상이 여러개라면?
반환값이 Tuple인 객체를 반환하기 때문에 이렇게 사용해야 한다.

List<Tuple> list = jpaQueryFactory.select(item.name, item.price).from(item).orderBy(item.name.desc()).fetch();

가만보니까 이렇게 특정 칼럼만 받아서 쓰는 서비스 로직이 따로 있을거란 생각이 든다.
그때 그냥 DTO를 써주면 어떨까?

빈 생성

  • 프로퍼티 접근(Setter)
  • 필드 접근
  • 생성자 사용
    객체를 생성하는 방법 3가지 이다. 이것을 통해 그리고 Projections를 사용하여 dto객체를 생성해주면 될듯 하다.

주의❗️ 빈 생성자를 무조건적으로 넣어줘야 한다.

기본값이 빈 생성자라서 명시적으로 넣어주지 않았는데
다음과 같은 에러가 발생한다. 😱


protected 생성자 넣었을때 또 protected 에러를 발생시킨다.

그래서 lombok의 @NoArgsConstructor를 넣어주었더니 정상 실행이 된다.
public 접근제어자여야 한다.

@Test
@DisplayName("projection dto")
void dtoTest() {
        List<ItemDto> result = jpaQueryFactory.select(
        Projections.bean(ItemDto.class,item.name.as("name"), item.price))
        .from(item).fetch();
}

프로퍼티 접근 방식인데 여기선 Projections.bean을 사용한다. 이것이 setter 메소드를 사용해서 값을 채우는 방식이다.

필드 직접 접근 방식은 Projections.fields()를 사용하면 된다.

생성자 접근 방식은 Projections.constructor()를 사용하는데 지정한 프로젝션과 파라미터 순서가 같은 생성자여야 잘 동작한다.

수정, 삭제 배치 쿼리

이부분은 그냥 영속성 컨텍스트에서 삭제, 수정 해주는것이 더 편리할것 같아서 따로 정리하지는 않고 읽는 단계로 넘어가겠다.

동적 쿼리

여기가 내가 많이 생각했고 어떻게 해야 조건문으로 쿼리를 처리할지에 대한 고민을 했던게 이 부분인것 같다.

@Test
@DisplayName("동적 쿼리")
void 동적쿼리_Test() {
    SearchParam param = new SearchParam();
    param.setName(null);
    param.setPrice(200);

    BooleanBuilder booleanBuilder = new BooleanBuilder();
    if (!ObjectUtils.isEmpty(param.getName())) {
        System.out.println(param.getName());
        booleanBuilder.and(item.name.eq(param.getName()));
    }

    if (!ObjectUtils.isEmpty(param.getPrice())) {
        System.out.println(param.getPrice());
        booleanBuilder.and(item.price.eq(param.getPrice()));
    }

    List<Item> result = jpaQueryFactory.selectFrom(item).where(booleanBuilder).fetch();
}

BooleanBuilder를 사용하여 조건에따른 조건을 할당해서 조회해줄수가 있다.
이것을 할줄 몰라서 Mybatis를 고집했던 이유도 없지않아 있는것 같다.

JPQL이 기본인 CRUD보다 훨씬 중요하고 이것을 알아야 원하는대로 select 쿼리를 날려줄 수가 있다.
그러면서 동시에 이 QueryDSL을 쿼리로 만드는게 복잡하고 어렵다고 생각해서 mybatis에서 또는 그냥 @Query에서 못벗어난? 것 같다. 😭

여기에 리팩토링을 추가한다면 조건문 하나당 로직을 메서드로 분리하여 수행해주면 편할것이다.

profile
💻 많이 짜보고 많이 경험해보자 https://lsj8367.tistory.com/ 블로그 주소 옮김

0개의 댓글