일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- @IdClass
- JPA주의사항
- json
- REST API 규칙
- 스프링 부트 공식 문서
- 복합키
- json gson 차이
- REST란
- Filter
- git
- spring서버
- 1차캐시
- 인텔리제이
- jwt메서드
- github
- Unsatisfied dependency
- JPA
- uncheck Exception
- 스프링 부트 기능
- 빈생성안됨
- Spring Spring boot 차이
- 최종 프로젝트
- Error creating bean with name
- queryDSL
- Q 클래스
- jpa회원가입
- 스프링부트오류
- JoinColumn
- jpa에러
- ERD 작성
- Today
- Total
Everyday Dev System
QueryDSL 활용 코드 본문
Repository 인터페이스의 메소드 이름을 기반으로 쿼리를 생성하는 기능에 한계가 있는 경우,
Q 클래스를 사용하여 더 복잡하고 유연한 쿼리를 작성할 수 있습니다.
QueryDSL을 사용할 때, @Entity 어노테이션이 붙은 클래스에 대해 자동으로 Q 클래스가 생성됩니다.
Q 클래스는 해당 엔티티 클래스와 동일한 패키지에 위치하며, 자동으로 생성됩니다.
따라서, QUser 클래스를 사용하기 위해서는 해당 클래스를 import하고, 인스턴스를 생성해야 합니다.
아래 예시에서 QThread 클래스는 me.chaeyoung.jpa.thread 패키지에 위치하고 있기 때문에, ThreadServiceImpl 클래스 내부에서는 QThread 클래스를 import하지 않고도 사용할 수 있습니다.
아래처럼 Thread 엔티티 클래스를 만들면,
아래와 같은 QThread 클래스가 생성됩니다.
Q 클래스가 생성되는 위치는 아래 글을 참조해주시기 바랍니다.
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 |