JPA에서 동적 쿼리를 생성하는 방법은 보통 JPQL, Criteria, Specification, QueryDSL 이렇게 4가지가 있다.
근데 이번과제는

JPQL을 이용하라고한다.
JPQL만 쓸 거라면, 경우의 수를 모두 if로 나눠서 JPQL 쿼리를 조립하거나, 동적 쿼리를 구성하는 방식으로 해결할 수 있는데..,
지금 조건이 1.날씨, 2.수정일 시작, 3.수정일 끝 총 3가지에 모두가 옵셔널하기때문에
총 8개의 경우의 수가 나와서 동적 쿼리 만들어보려고함.

먼저 서비스단에서는 TodoRepository의 형태에 관심을 가질필요가없어서 TodoRepository에서 의존성 주입해주는 커스텀한 레포지토리 인터페이스 만들어줌

해당 TodoSearchRepository는 필요한 함수가 하나있고,
거기서 필요한 변수에 맞게 넣어준다..
package org.example.expert.domain.todo.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.example.expert.domain.todo.entity.Todo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public class TodoSearchRepositoryImpl implements TodoSearchRepository {
private final EntityManager entityManager;
@Override
public Page<Todo> searchTodos(String weather, LocalDate startDate, LocalDate endDate, Pageable pageable) {
StringBuilder jpql = new StringBuilder("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ");
Map<String, Object> params = new HashMap<>();
List<String> whereList = new ArrayList<>();
//날씨o
if (weather != null && !weather.isBlank()) {
whereList.add("t.weather = :weather");
params.put("weather", weather);
}
// 시작날짜
if (startDate != null) {
whereList.add("t.modifiedAt >= :startDate");
params.put("startDate", startDate);
}
//끝날짜
if (endDate != null) {
whereList.add("t.modifiedAt <= :endDate");
params.put("endDate", endDate);
}
//조건 하나라도 있으면 적용해줘야함
if (!whereList.isEmpty()) {
jpql.append(" WHERE ").append(String.join(" AND ", whereList));
}
//정렬 조건 마지막에 넣어줌
jpql.append("ORDER BY t.modifiedAt DESC");
TypedQuery<Todo> query = entityManager.createQuery(jpql.toString(), Todo.class);
params.forEach(query::setParameter);
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
List<Todo> result = query.getResultList();
return new PageImpl<>(result, pageable, result.size());
}
}
보면알겠지만 각 조건에 대한 값 유무에 따라서 WHERE / AND 붙여서 조건만들어주고 쿼리 실행시킴
StringBuilder jpql = new StringBuilder("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ");
Map<String, Object> params = new HashMap<>();
List<String> whereList = new ArrayList<>();
여기서 jpql은 쿼리문을 쌓는 StringBuilder이고
Map은 뒤에 들어가는 변수값이 String이랑 LocalDate 두종류길래 value를 Object으로 받아줫음..
whereList는 String.join 쓰면 쿼리문 한방에 만들수있으니깐 만들어줘슴
어차피 그 JPQL에서 변수 지정해주는건 entityManager.createQuery 구문 지나야 해서..
TypedQuery<Todo> query = entityManager.createQuery(jpql.toString(), Todo.class);
params.forEach(query::setParameter);
이 JPQL을 실행할 준비된 쿼리 객체를 만들어줘! 결과는 Todo야! 라고 알려주고
쿼리에 있는 :파라미터들에 실제 값들을 전부 넣어조삼 하는거임
실제로 JPQL 실행준비??를 하는 곳 ㅇ_ㅇ
근데 우리는 페이지네이션 옵션ㅇ이 또 있어서
그걸 적용시켜줘야함
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
요건데 1. 어디서부터 읽을거야!
2. 얼만큼 읽을거야! 해주는거임
List<Todo> result = query.getResultList();
누가봐도 실행결과 가져옴
끝
예시도있으면좋을거같아서 지피티 시켰음
SELECT t FROM Todo t
LEFT JOIN FETCH t.user u
WHERE t.weather = :weather AND t.modifiedAt >= :startDate AND t.modifiedAt <= :endDate
ORDER BY t.modifiedAt DESC
SELECT t.id, t.title, t.content, t.weather, t.modified_at, t.user_id, u.id, u.username, u.email
FROM todo t
LEFT JOIN user u ON t.user_id = u.id
WHERE t.weather = 'SUNNY'
AND t.modified_at >= '2024-01-01'
AND t.modified_at <= '2024-12-31'
ORDER BY t.modified_at DESC
LIMIT 10 OFFSET 20;

JPQL로 동적쿼리 만드는 법 잘 몰랐는데 배워가요