개인과제로 만들었었던 프로젝트로 기본적인 스프링 부트 crud 구현에서 조회 부분에 querydsl을 적용해보려고 한다.
QueryDsL 이란
정적 타입을 이용하여, SQL과 같은 쿼리를 코드형태로 생성할 수 있도록 해주는 오픈소스 빌더 API이다.
JPQL처럼 query문을 문자열로 작성하는 하드 코딩이 아닌 코드형태로 작성을하여 컴파일 시간에 에러가 식별되어 리스크를 줄일 수가 있다.
JPQL을 사용한 quey문
public interface PersonRepository extends JpaRepository<Person, Long>{
/* 변수 바인딩 시, ?시퀀스 사용하는 경우 */
@Query("select p from Person p where p.firstName = ?1 and p.lastName = ?2")
Person findPerson(String firstName, String lastName);
/* 변수 바인딩 시, :이름 사용하는 경우 */
@Query("select p from Person p where p.firstName = :firstName and p.lastName = :lastName")
Person findPerson2(@Param("firstName") String firstName, @Param("lastName") String lastName);
}
QueryDSL을 사용한 query문
@PersistenceContext
EntityManager em;
public List<Person> selectPersonByNm(String firstNm, String lastNm){
JPAQueryFactory jqf = new JPAQueryFactory(em);
QPerson person = QPerson.person;
List<Person> personList = jpf
.selectFrom(person)
.where(person.firstName.eq(firstNm)
.and(person.lastName.eq(lastNm))
.fetch();
return personList;
}
QueryDSL을 사용하기 앞서 QueryDSL을 연동을 해주어야하는데 의존성과 설정을 조금 추가해주어야한다.
참고 레퍼런스
의존성과 설정후에는 gradle의 빌드를 통하여
QueryDSL에서 query를 위해 사용할 Qclass가 생성이된다.
Q객체란?
JPAAnnotationProcessor가 컴파일 시점에 @Entity와 @Embeddedable과 같은 어노테이션이 걸린 클래스에 대해, 미리 생성하는 쿼리 객체
해당 객체에 쿼리문을 메서드 형태로 호출하여 SQL문을 짜듯이 코드로 쿼리문 작성이 가능하다.
@Override
@Transactional(readOnly = true)
public List<TodoListResponseDto> getAllTodo() {
return toDoRepository.findAll()
.stream()
.map(TodoListResponseDto::new)
.sorted(Comparator.comparing(TodoListResponseDto::getModifiedAt, Comparator.nullsLast(Comparator.reverseOrder())))
.toList();
}
이것이 리팩토리 전 service단에서 사용될 모든 todo라는 게시글을 가져오는 메서드로 서비스단에서 정렬을 하게 되었다. 이 정렬을 레포지토리 단에서 정렬해서 가져오기 위하여 그리고 page 기능을 추가하여 페이지를 적용시켜 보겠다.
먼저 QueryDsL을 이용하여 repository에서 가져올 때 Todo를 modifiedAt순으로 가져오게 리팩토링을 하였다.
public interface TodoRepository extends JpaRepository<Todo, Long> , TodoRepositoryQuery{
}
//TodoRepositoryQuery를 todoRepository가 상속하게 만들고
public interface TodoRepositoryQuery {
List<Todo> findAllTodo();
}
// 인터페이스를 구현해주었다.
// 구현체를 통하여 구현을 해주고
@Repository
@RequiredArgsConstructor
public class TodoRepositoryQueryImpl implements TodoRepositoryQuery{
private final JPAQueryFactory jpaQueryFactory;
@Override
public List<Todo> findAllTodo(Pageable pageable){
QTodo qTodo = todo;
OrderSpecifier<?> orderSpecifier = new OrderSpecifier<>(Order.DESC, todo.modifiedAt);
return jpaQueryFactory
.select(qTodo)
.from(qTodo)
//.orderBy(qTodo.modifiedAt.desc().nullsLast())
//처음 강의에서는 직접 넣어서 수정일 순으로 구현을 하였지만.
//다른강의에서는 OrderSpecifier을 이용하여 수정일 내림차순을 구현하게되었다.
.orderBy(orderSpecifier)
.fetch();
}
}
// 서비스에서 구현을 완료해주었다.
@Override
@Transactional(readOnly = true)
public List<TodoListResponseDto> getAllTodo() {
return todoRepository.findAllTodo()
.stream()
.map(TodoListResponseDto::new)
.collect(Collectors.toList());
}
이렇게 OrderSpecifier을 이용하여 동적인 정렬을 사용하면 좋은 장점은
코파일럿 의 답변으로는
- 동적 정렬: OrderSpecifier를 활용하면 동적으로 정렬 기준을 설정할 수 있습니다. 즉, 사용자의 요청에 따라 다양한 칼럼을 기준으로 정렬할 수 있습니다. 예를 들어, 조회수 순, 최신순, 스크랩 순 등 다양한 정렬 조건을 동적으로 처리할 수 있습니다.
- 페이징과 함께 사용: OrderSpecifier를 통해 정렬 정보를 생성하면, 페이징 처리와 함께 사용할 수 있습니다. 사용자가 원하는 정렬 순서를 적용하여 페이징된 결과를 반환할 수 있습니다.
- Expression 객체 활용: OrderSpecifier는 정렬 기준이 되는 칼럼의 Path를 표현하는 Expression 객체를 필요로 합니다. 이를 통해 정렬할 엔티티의 칼럼과 그 칼럼이 속한 엔티티를 합쳐서 표현합니다. 예를 들어, postId를 정렬 기준으로 설정하면, Post 엔티티의 postId가 Path가 됩니다
그 이후 pageable을 구현하기 위하여 PageDTO를 만들어주고 리스트를 불러오는 부분에 pagable을 구현해주었다.
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class PageDTO {
private final Integer currentPage;
private final Integer size;
private String sortBy;
public Pageable toPageable() {
if (Objects.isNull(sortBy)) {
return PageRequest.of(currentPage - 1, size);
} else {
return PageRequest.of(currentPage - 1, size, Sort.by(sortBy).descending());
}
}
public Pageable toPageable(String sortBy) {
return PageRequest.of(currentPage - 1, size, Sort.by(sortBy).descending());
}
}
pageDTO에서 우리는 페이지가 1부터 시작하지만 시스템에서는 0부터 시작하기때문에 그것에 맞춰 구현해주기위해서 -1을 해주었다.
public interface TodoRepositoryQuery {
List<Todo> findAllTodo(Pageable pageable);
}
//
@Repository
@RequiredArgsConstructor
public class TodoRepositoryQueryImpl implements TodoRepositoryQuery{
private final JPAQueryFactory jpaQueryFactory;
@Override
public List<Todo> findAllTodo(Pageable pageable){
QTodo qTodo = todo;
OrderSpecifier<?> orderSpecifier = new OrderSpecifier<>(Order.DESC, todo.modifiedAt);
return jpaQueryFactory
.select(qTodo)
.from(qTodo)
//.orderBy(qTodo.modifiedAt.desc().nullsLast())
.orderBy(orderSpecifier)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}
}
//
@Override
@Transactional(readOnly = true)
public List<TodoListResponseDto> getAllTodo(Pageable pageable) {
return todoRepository.findAllTodo(pageable)
.stream()
.map(TodoListResponseDto::new)
.collect(Collectors.toList());
}
API요청에서 pageDTO를 받아와 컨트롤러에서 pageDTO.topageable()로 가져와 offset과 limit로 조회할 데이터 시작위치와 한페이조회할 데이터수를 가져와서 pageable을 구현하였다.
'내일배움캠프' 카테고리의 다른 글
내일배움캠프 최종프로젝트 - 캐싱 적용-1 (0) | 2024.04.04 |
---|---|
내일배움캠프TIL-Docker를 활용해 MySQL을 컨테이너로 띄워 사용하자 (0) | 2024.03.13 |
내일배움캠프TIL - @RestControllerAdvice (0) | 2024.02.14 |
내일배움캠프TIL - ResponseEntity (0) | 2024.02.06 |
내일배움캠프TIL- 영속성 전이 (0) | 2024.01.30 |