Everyday Dev System

QueryDSL의 JPAQueryFactory를 활용한 조회문 본문

내배캠 주요 학습/JPA 심화

QueryDSL의 JPAQueryFactory를 활용한 조회문

chaeyoung- 2023. 8. 2. 13:44

 

JPAQueryFactory을 빈으로 등록하기 위해 configuration 클래스를 생성한다.

package me.chaeyoung.jpa.configuration;


import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JPAConfiguration {

  @PersistenceContext
  EntityManager entityManager;
  
  @Bean
  public JPAQueryFactory jpaQueryFactory(){
    return new JPAQueryFactory(entityManager);    
  }
}

 

 

먼저 repository 인터페이스 코드이다.

0. ThreadRepository

package me.chaeyoung.jpa.thread;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.RepositoryDefinition;

@RepositoryDefinition(domainClass = Thread.class, idClass = Long.class)

public interface ThreadRepository extends JpaRepository<Thread, Long>
    , QuerydslPredicateExecutor<Thread>, ThreadRepositoryQuery {


}

1. ThreadRepositoryQuery

package me.chaeyoung.jpa.thread;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;


public interface ThreadRepositoryQuery {
  Page<Thread> search(ThreadSearchCond cond, Pageable pageable);

}

 

 

 

조회 조건에 필요한 필드를 묶어놓은 클래스이다.

2. ThreadSearchCond

package me.chaeyoung.jpa.thread;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ThreadSearchCond {
  // 조건에 필요한 필드들을 모아놓음.

  private Long channelId; // 채널 Id
  private Long mentionedUserId; // 멘션된 user_id


}

 

 

 

1번의 인터페이스를 구현한 구현체이다.

3. ThreadRepositoryQueryImpl

package me.chaeyoung.jpa.thread;

import static me.chaeyoung.jpa.channel.QChannel.channel;
import static me.chaeyoung.jpa.thread.QThread.thread;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Wildcard;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;

@RequiredArgsConstructor
public class ThreadRepositoryQueryImpl implements ThreadRepositoryQuery {

  private final JPAQueryFactory jpaQueryFactory;


  @Override
  public Page<Thread> search(ThreadSearchCond cond, Pageable pageable) {
    var query = query(thread, cond)
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize());

    // Wildcard.count 는 count 쿼리를 날린다는 의미
    var countQuery = query(Wildcard.count, cond);

    // 실제로 받아올 스레드는 query()를 fetch()만 하면 됨.
    var threads = query.fetch();
    long totalSize = countQuery.fetch().get(0);

    // PageableExecutionUtils.getPage() 메서드를 통해서 threads 를 페이징 객체로 감싸서 반환
    return PageableExecutionUtils.getPage(threads, pageable, () -> totalSize);
  }

  private <T> JPAQuery<T> query(Expression<T> expr, ThreadSearchCond cond) {
    // jpaQueryFactory.select() 메서드와 형태 동일하게 구성함.
    return jpaQueryFactory.select(expr)
        .from(thread)
        .leftJoin(thread.channel, channel).fetchJoin()
        // fetchJoin() 으로 해야 LAZY 로 설정된 애를 조회해 옴.
        .where(
            // getChannelId 과 getMentionedUserId 가 null 이면 에러 발생
            this.channelIdEq(cond.getChannelId()),
            this.mentionedUserIdEq(cond.getMentionedUserId())
        );
  }


  // query() 메서드에 getChannelId 과 getMentionedUserId 가 null 이면 에러 발생
  // 하므로 이를 체크해주는 메서드임
  // eq() 메서드는 반환값이 BooleanExpression 임.
  private BooleanExpression channelIdEq(Long channelId) {
    // channelId가 null 이 아니라면 전자 실행 후 반환 , null 이면 null 을 반환
    return Objects.nonNull(channelId) ? thread.channel.id.eq(channelId) : null;
  }

  private BooleanExpression mentionedUserIdEq(Long mentionedUserId) {
    return Objects.nonNull(mentionedUserId) ? thread.mentions.any().user.id.eq(mentionedUserId)
        : null;
  }
}

 

 

 

페이징을 위한 DTO 클래스

4. PageDTO

package me.chaeyoung.jpa.common;

import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;


@RequiredArgsConstructor
@AllArgsConstructor
public class PageDTO {

  private final Integer currentPage;

  private final Integer size;
  private String sortBy;

  public Pageable toPageable() {
    return PageRequest.of(currentPage - 1, size, Sort.by(sortBy).descending());
    // 한 페이지에 (size)개씩
  }

}

 

 

 

 

 

서비스 인터페이스

5. ThreadService

package me.chaeyoung.jpa.thread;

import java.util.List;
import me.chaeyoung.jpa.channel.Channel;
import me.chaeyoung.jpa.common.PageDTO;
import org.springframework.data.domain.Page;

public interface ThreadService {

  List<Thread> selectNotEmptyThreadList(Channel channel);

  Page<Thread> selectMentionedThreadList(Long userId, PageDTO pageDTO);

  Thread insert(Thread thread);
}

 

 

 

 

서비스 인터페이스를 구현한 클래스 

6. ThreadServiceImpl

package me.chaeyoung.jpa.thread;

import com.mysema.commons.lang.IteratorAdapter;
import java.util.List;
import lombok.RequiredArgsConstructor;
import me.chaeyoung.jpa.channel.Channel;
import me.chaeyoung.jpa.common.PageDTO;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ThreadServiceImpl implements ThreadService {


  private final ThreadRepository threadRepository;

  private final ThreadRepositoryQuery threadRepositoryQuery;

  @Override
  public Page<Thread> selectMentionedThreadList(Long userId, PageDTO pageDTO) {
    var cond = ThreadSearchCond.builder().mentionedUserId(userId).build();
    // 페이징을 할 때, PageRequest 로 요청 받아서 응답은 Pageable 타입으로 반환해야 하므로 -> pageDTO.toPageable()
    return threadRepositoryQuery.search(cond, pageDTO.toPageable());
  }

  @Override
  public List<Thread> selectNotEmptyThreadList(Channel channel) {
    var thread = QThread.thread;

    // 메세지가 비어있지 않은 해당 채널의 스레드 목록
    var predicate = thread.channel.eq(channel).and(thread.message.isNotEmpty());
    var threads = threadRepository.findAll(predicate);
    // predicate 조건에 따른 스레드를 findall() 해와라. 그리고 반환하라

    return IteratorAdapter.asList(threads.iterator());
  }

  @Override
  public Thread insert(Thread thread) {
    return threadRepository.save(thread);
  }
}

'내배캠 주요 학습 > JPA 심화' 카테고리의 다른 글

@Transaction propagation  (0) 2023.08.04
JPA 쿼리 최적화  (0) 2023.08.04
JPA 활용 장점  (0) 2023.08.01
Auditing 활용하기  (1) 2023.08.01
QueryDSL 활용 코드  (0) 2023.08.01