Everyday Dev System

QueryDSL 활용 코드 본문

내배캠 주요 학습/JPA 심화

QueryDSL 활용 코드

chaeyoung- 2023. 8. 1. 17:21

Repository 인터페이스의 메소드 이름을 기반으로 쿼리를 생성하는 기능에 한계가 있는 경우,

Q 클래스를 사용하여 더 복잡하고 유연한 쿼리를 작성할 수 있습니다.

 

 

QueryDSL을 사용할 때, @Entity 어노테이션이 붙은 클래스에 대해 자동으로 Q 클래스가 생성됩니다.

Q 클래스는 해당 엔티티 클래스와 동일한 패키지에 위치하며, 자동으로 생성됩니다.

 

따라서, QUser 클래스를 사용하기 위해서는 해당 클래스를 import하고, 인스턴스를 생성해야 합니다.

 

아래 예시에서 QThread 클래스는 me.chaeyoung.jpa.thread 패키지에 위치하고 있기 때문에, ThreadServiceImpl 클래스 내부에서는 QThread 클래스를 import하지 않고도 사용할 수 있습니다.

 

아래처럼 Thread 엔티티 클래스를 만들면,

아래와 같은 QThread 클래스가 생성됩니다.

 

 

Q 클래스가 생성되는 위치는 아래 글을 참조해주시기 바랍니다.

https://cdev.tistory.com/entry/QueryDSL%EC%97%90%EC%84%9C-Q-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%8A%94-%EC%96%B4%EB%94%94%EC%97%90-%EC%9C%84%EC%B9%98%ED%95%B4%EC%9E%88%EC%9D%84%EA%B9%8C

 

QueryDSL에서 Q 클래스는 어디에 위치해있을까?

chat GPT에도 물어보고 해당 경로로 가서 봤지만, 찾지 못했다. 결국 파일 디렉터리 하나하나 들어가서 찾아보았다. build 폴더도 아닌! out 폴더 내에 있었다. 여기서 궁금한 점이 생겼다. 위에 클래

cdev.tistory.com

 

 

package me.chaeyoung.jpa.thread;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.util.LinkedHashSet;
import java.util.Set;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import me.chaeyoung.jpa.channel.Channel;
import me.chaeyoung.jpa.mention.Mention;
import me.chaeyoung.jpa.user.User;

// lombok
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)

// jpa
@Entity
public class Thread {

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

  @Column(length = 500)
  private String message;

  @Builder
  public Thread(String message) {
    this.message = message;
  }

  @ManyToOne
  @JoinColumn(name = "channel_id")
  private Channel channel;

  @OneToMany(mappedBy = "thread", cascade = CascadeType.ALL, orphanRemoval = true)
  Set<Mention> mentions = new LinkedHashSet<>();

  public void setChannel(Channel channel) {
    this.channel = channel;
    channel.addThread(this);
  } // 채널과 스레드는 양방향이므로 연관관계 편의 메서드 둘다 생성

  public void addMention(User user) {
    var mention = Mention.builder().user(user).thread(this).build();
    this.mentions.add(mention);
    user.getMentions().add(mention);
  }

}

 

 

 

 

 

이 Q 클래스를 Service 클래스에서 사용하여 QueryDSL을 활용할 수 있습니다.

 

먼저 ThreadService 인터페이스를 만들고,

이를 구현하는 클래스인 ThreadServiceImpl를 작성하도록 하겠습니다.

package me.chaeyoung.jpa.thread;

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

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

}
package me.chaeyoung.jpa.thread;

import java.util.List;
import me.chaeyoung.jpa.channel.Channel;
import me.chaeyoung.jpa.user.User;

public interface ThreadService {

  List<Thread> selectMentionedThreadList(User user);

  List<Thread> selectNotEmptyThreadList(Channel channel);

  Thread insert(Thread thread);
}
package me.chaeyoung.jpa.thread;

import com.mysema.commons.lang.IteratorAdapter;
import java.util.List;
import me.chaeyoung.jpa.channel.Channel;
import me.chaeyoung.jpa.user.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ThreadServiceImpl implements ThreadService {

  @Autowired
  ThreadRepository threadRepository;

  @Override
  public List<Thread> selectMentionedThreadList(User user) {
    // 내가 멘션된 스레드 리스트를 받아와야 함.

    // QueryDSL에서 사용할 Q 클래스를 선언하여 thread 변수에 할당
    var thread = QThread.thread;

    // thread.mentions.any().user.eq(user)는 "스레드의 멘션들 중에 어떤 멘션의 사용자가 주어진 user와 동일한가?"를 나타내는 조건식입니다.
    var predicate = thread.mentions.any().user.eq(user);

    var threads = threadRepository.findAll(predicate);
    // threads 가 List가 아니여서 타입을 바꿔서 반환 필요

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

  @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);

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

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

 

 

 

코드 설명

  @Override
  public List<Thread> selectMentionedThreadList(User user) {
    var thread = QThread.thread;

    var predicate = thread.mentions.any().user.eq(user);

    var threads = threadRepository.findAll(predicate);
    // threads 가 List가 아니여서 타입을 바꿔서 반환 필요가 있음.

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

 

predicate는 QueryDSL의 조건식을 나타내는 객체입니다.

 

즉, 데이터베이스에서 조회할 때 적용할 필터 조건을 정의하는 역할을 합니다.

여기서 조건식은 스레드의 멘션들 중에 어떤 멘션의 사용자가 파라미터로 넘어온 user와 동일한가? 의 조건식입니다.

 

 

 

  @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());
  }

 

 

테스트 코드 

코드를 모두 작성하고 테스트 코드를 실행해보겠습니다.

package me.chaeyoung.jpa.thread;


import java.util.List;
import me.chaeyoung.jpa.channel.Channel;
import me.chaeyoung.jpa.channel.Channel.Type;
import me.chaeyoung.jpa.channel.ChannelRepository;
import me.chaeyoung.jpa.mention.Mention;
import me.chaeyoung.jpa.user.User;
import me.chaeyoung.jpa.user.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ThreadServiceImplTest {

  @Autowired
  UserRepository userRepository;

  @Autowired
  ThreadService threadService;

  @Autowired
  ChannelRepository channelRepository;

  @Test
  void getMentionsThreadList() {
    // given
    var newUser = User.builder().username("new user").password("new pass").build();
    var savedUser = userRepository.save(newUser);
    var newThread1 = Thread.builder().message("message1").build();
    newThread1.addMention(newUser);
    threadService.insert(newThread1);

    var newThread2 = Thread.builder().message("message2").build();
    newThread2.addMention(newUser);
    threadService.insert(newThread2);

    // when
    var mentionedThreads = savedUser.getMentions().stream().map(Mention::getThread).toList();

    // then
    System.out.println(mentionedThreads);
    assert mentionedThreads.containsAll(List.of(newThread1, newThread2));
  }

  @Test
  void getNotEmptyThreadList() {
    // given
    var newChannel = Channel.builder().name("c1").type(Type.PUBLIC).build();
    var savedChannel = channelRepository.save(newChannel);
    var newThread1 = Thread.builder().message("message1").build();
    newThread1.setChannel(savedChannel);
    threadService.insert(newThread1);

    var newThread2 = Thread.builder().message("").build();
    newThread2.setChannel(newChannel);
    threadService.insert(newThread2);

    // when
    // user 가 맨션된 모든 스레드를 조회함.
    //var mentionedThreads = savedUser.getMentions().stream().map(Mention::getThread).toList();
    var notEmptyThreads = threadService.selectNotEmptyThreadList(savedChannel);

    // then
    assert !notEmptyThreads.contains(newThread2);
  }
}

 

 

 

 

User 클래스를 이용한 예제 코드

더보기

 

<< 2가지 메서드 >>

- 사용자 이름으로 조회하는 메소드 

- 사용자 나이가 30 이상인 사용자들을 동적으로 조회하는 메소드

Q 클래스를 활용하여 쿼리를 작성하고 실행합니다.

이를 통해 동적으로 쿼리를 작성하고 데이터를 검색할 수 있습니다.

 

 

<< 코드 설명 >>

 

        QUser qUser = QUser.user;

QUser 클래스는 QueryDSL에 의해 자동으로 생성된 Q 클래스입니다.

 


        Predicate predicate = qUser.age.greaterThan(age);

        return userRepository.findAll(predicate);

predicate는 QueryDSL의 조건식을 나타내는 객체입니다.

즉, 데이터베이스에서 조회할 때 적용할 필터 조건을 정의하는 역할을 합니다.

 

public interface UserRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {
    // 사용자 이름으로 조회하는 메소드
    List<User> findByName(String name);

    // 동적 쿼리를 작성하는 메소드 (Q 클래스 활용)
    List<User> findAll(Predicate predicate);
}

 

1. Service 단에서 활용 가능

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public List<User> getUsersWithAgeGreaterThan(int age) {
        // 동적 쿼리 (Q 클래스 활용)
        QUser qUser = QUser.user;
        Predicate predicate = qUser.age.greaterThan(age);
        return userRepository.findAll(predicate);
    }
}

 

 

2. Controller 단에서 활용 가능

@RestController
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @GetMapping("/users")
    public List<User> getUsers(@RequestParam(required = false) String name) {
        if (name != null) {
            // 사용자 이름으로 조회
            return userRepository.findByName(name);
        } else {
            // 동적 쿼리 (예시: 사용자 나이가 30 이상인 경우)
            QUser qUser = QUser.user;
            Predicate predicate = qUser.age.goe(30);
            return userRepository.findAll(predicate);
        }
    }
}

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

JPA 활용 장점  (0) 2023.08.01
Auditing 활용하기  (1) 2023.08.01
QueryDsl 기초  (0) 2023.08.01
Repository 에 기능 추가하기  (0) 2023.07.31
JpaRepository 기능 제한하기  (0) 2023.07.31