Sorting

최종윤·2023년 1월 7일

JPA

목록 보기
13/15

https://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-part-six-sorting/

데이터베이스 쿼리의 쿼리 결과를 정렬하고 예제 응용 프로그램의 검색 기능에 정렬 지원을 추가하는 방법을 배워봅시다.

메서드 이름을 사용하여 쿼리 결과 정렬

메소드 이름 전략의 쿼리 생성을 사용하여 데이터베이스 쿼리를 생성하는 경우 OrderBy 키워드를 사용하여 데이터베이스 쿼리의 쿼리 결과를 정렬할 수 있습니다. 다음 단계에 따라 OrderBy 키워드를 사용할 수 있습니다:

  1. 쿼리 메서드의 메서드 이름에 OrderBy 키워드를 추가합니다.

  2. 속성 이름을 쿼리 메서드의 메서드 이름에 추가하고 첫 번째 문자를 대문자로 변환합니다. 작업관리 항목의 제목을 사용하여 쿼리 결과를 정렬하려면 다음 문자열을 추가해야 합니다: 쿼리 메서드의 메서드 이름 제목입니다.

  3. 정렬 방향을 설명합니다. 쿼리 결과를 오름차순으로 정렬하려면 쿼리 메서드의 메서드 이름에 키워드 As를 추가해야 합니다. 반면, 쿼리 결과를 내림차순으로 정렬하려면 쿼리 방법의 메서드 이름에 키워드 Desc를 추가해야 합니다.

  4. 여러 속성을 사용하여 쿼리 결과를 정렬해야 하는 경우 2단계로 돌아가야 합니다.

다음 예제에서는 OrderBy 키워드를 사용하는 방법을 보여 줍니다:

예 1

메서드 매개 변수로 title이 지정된 작업관리 항목으로 돌아가는 쿼리 메서드를 만들었습니다. 제목 필드 값을 사용하여 해당 쿼리 메서드의 쿼리 결과를 오름차순으로 정렬하려면 다음 코드를 사용해야 합니다:
``
interface TodoRepository extends Repository<Todo, Long> {

List<Todo> findByTitleOrderByTitleAsc(String title);

}
``

예 2

메서드 매개 변수로 제목이 지정된 작업관리 항목으로 돌아가는 쿼리 메서드를 만들었습니다. Title 필드의 값을 사용하여 쿼리 메서드의 쿼리 결과를 오름차순으로 정렬하고 Description 필드의 값을 사용하여 내림차순으로 정렬하려면 다음 코드를 사용해야 합니다:
``
interface TodoRepository extends Repository<Todo, Long> {

List<Todo> findByTitleOrderByTitleAscDescriptionDesc(String title);

}
``

예 3

예제 응용프로그램의 검색 기능은 제목이나 설명에 지정된 검색어가 포함된 작업관리 항목으로 돌아갑니다. 제목 필드 값을 사용하여 검색 결과를 오름차순으로 정렬하려면 다음 쿼리 방법을 리포지토리 인터페이스에 추가해야 합니다:
interface TodoRepository extends Repsitory<Todo, Long> { List<Todo.> findByDescriptionContainsOrTitleContainsAllIgnoreCaseOrderByTitleAsc (String description, String title); }
Contains는 각각 field에 붙여주고 AllIgnoreCase와 OrderBy를 뒤에 붙입니다.
이 메서드가 다른 코드에서(Service) 자주 쓰인다면 코드 가독성이 떨어질것입니다.

쿼리 문자열을 사용하여 쿼리 결과 정렬

명명된 쿼리 또는 @Query 주석을 사용하여 데이터베이스 쿼리를 만드는 경우 쿼리 문자열에 정렬 논리를 지정할 수 있습니다.

데이터베이스 쿼리가 @Query 주석을 사용하는 명명된 쿼리 또는 네이티브 쿼리인 경우 쿼리 문자열에 정렬 논리를 지정해야 합니다.

예제 응용 프로그램의 검색 기능은 대소문자를 구분하지 않습니다. 제목이나 설명에 지정된 검색어가 포함된 작업관리 항목으로 돌아갑니다. 다음 예제는 기존 JPQL 및 SQL 쿼리를 수정하여 쿼리 결과를 정렬하는 방법을 보여줍니다:

Example 1:
If we want to modify an existing JPQL query to sort the query results in ascending order by using the value of the title field, we have to use the JPQL ORDER BY clause.

SELECT t FROM Todo t WHERE LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%')) ORDER BY t.title ASC

Example 2:
If we want to modify an existing SQL query to sort the query results in ascending order by using the value of the title field, we have to use the SQL ORDER BY clause.

SELECT * FROM todos t WHERE LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%')) ORDER BY t.title ASC

정렬 클래스를 사용하여 쿼리 결과 정렬

데이터베이스 쿼리가 @Query 주석을 사용하는 명명된 쿼리나 네이티브 쿼리가 아닌 경우 정렬 클래스를 사용하여 쿼리 결과를 정렬할 수 있습니다. 기본적으로 데이터베이스 쿼리의 정렬 옵션을 설명하는 사양 클래스입니다.

다음 단계에 따라 쿼리 결과를 정렬할 수 있습니다:
1. 호출되는 db query의 정렬 옵션을 설명하는 Sort 객체를 얻는다.
2. Sort 객체를 repository method의 method parameter로 전달한다.

Sort 개체 가져오기

정렬 옵션을 수동으로 지정하거나 Spring Data Web Support를 사용하여 정렬 개체를 얻을 수 있습니다.

먼저 수동으로 정렬 옵션을 지정합니다.

수동으로 정렬 옵션 지정

정렬 옵션을 수동으로 지정하려면 Spring Data JPA 리포지토리에서 반환된 쿼리 결과를 정렬하려는 서비스 클래스(또는 다른 구성 요소)가 Sort 개체를 생성하여 호출된 리포지토리 메서드로 전달해야 합니다.

리포지토리의 소스 코드이 메서드를 사용하는 작업관리 검색 서비스 클래스는 다음과 같습니다:

``
@Service
final class RepositoryTodoSearchService implements TodoSearchService {

private final TodoRepository repository;

@Autowired
public RepositoryTodoSearchService(TodoRepository repository) {
    this.repository = repository;
}

@Transactional(readOnly = true)
@Override
public List<TodoDTO> findBySearchTerm(String searchTerm) {
    Sort sortSpec = orderBy();
     
    //Obtain search results by invoking the preferred repository method.
    List<Todo> searchResults = ...
     
    return TodoMapper.mapEntitiesIntoDTOs(searchResults);
}
 
private Sort orderBy() {
    //Create a new Sort object here.
}

}
``

orderBy()구현하는 법

Example 1:

If we must sort the query results in ascending order by using the value of the title field, we have to create the Sort object by using the following code:

private Sort orderBy() {
return new Sort(Sort.Direction.ASC, "title");
}

Example 2:

If we must sort the query results by in descending order by using the values of the title and description fields, we have to create the Sort object by using the following code:

private Sort orderBy() {
return new Sort(Sort.Direction.DESC, "title", "description");
}

Example 3:

If we want to sort the query results in descending order by using the value of the description field and in ascending order by using the value of the title field, we have to create the Sort object by using the following code:

private Sort orderBy() {
return new Sort(Sort.Direction.DESC, "description")
.and(new Sort(Sort.Direction.ASC, "title"));
}

스프링 데이터 웹 지원 사용

애플리케이션 컨텍스트 구성 클래스에 @EnableSpringDataWebSupport 주석을 달아 SpringData 웹 지원을 활성화할 수 있습니다. 예제 응용 프로그램의 지속성 계층을 구성하는 PersistenceContext 클래스의 관련 부분은 다음과 같습니다:

@Configuration @EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider") @EnableJpaRepositories(basePackages = { "net.petrikainulainen.springdata.jpa.todo" }) @EnableTransactionManagement @EnableSpringDataWebSupport class PersistenceContext { }

요청 매개 변수 또는 @SortDefault 주석에서 정렬 개체를 만들 수 있는
새 SortHandlerMethodArgumentResolver 인스턴스를 등록합니다. 즉, 정렬 요청 매개 변수의 값을 설정하여 정렬 로직을 지정할 수 있습니다. Spring Data JPA의 참조 문서에는 정렬 요청 파라미터의 내용이 다음과 같이 설명되어 있습니다:

Properties that should be sorted by in the format property,property(,ASC|DESC). Default sort direction is ascending. Use multiple sort parameters if you want to switch directions, e.g. ?sort=firstname&sort=lastname,asc.

Spring Data 웹 지원을 활성화한 후에는 Sort 객체를 컨트롤러 핸들러 메서드에 삽입할 수 있습니다. Spring Data 웹 지원을 활용하는 TodoSearchController 클래스의 소스 코드는 다음과 같습니다:

``
@RestController
final class TodoSearchController {

private final TodoSearchService searchService;

@Autowired
public TodoSearchController(TodoSearchService searchService) {
    this.searchService = searchService;
}

@RequestMapping(value = "/api/todo/search", method = RequestMethod.GET)
public List<TodoDTO> findBySearchTerm(@RequestParam("searchTerm") String searchTerm, 
                                      Sort sort) {
    return searchService.findBySearchTerm(searchTerm, sort);
}

}
``
작업관리 검색 컨트롤러는 작업관리 검색 서비스 개체에서 반환된 작업관리 항목의 정보를 가져옵니다. 저장소TodoSearchService 클래스는 TodoSearchService 인터페이스를 구현하며, findBySearchTerm() 메서드는 단순히 검색어와 정렬 개체를 호출된 리포지토리 메서드로 전달합니다.

``
@Service
final class RepositoryTodoSearchService implements TodoSearchService {

private final TodoRepository repository;

@Autowired
public RepositoryTodoSearchService(TodoRepository repository) {
    this.repository = repository;
}

@Transactional(readOnly = true)
@Override
public List<TodoDTO> findBySearchTerm(String searchTerm, Sort sort) {     
    //Obtain search results by invoking the preferred repository method.
    List<Todo> searchResults = ...
     
    return TodoMapper.mapEntitiesIntoDTOs(searchResults);
}

}
``

정렬 개체 사용

Sort 개체를 수동으로 만들거나 Spring Data 웹 지원을 사용하여 얻은 후에는 Sort 개체를 사용하여 쿼리 결과를 정렬하는 데이터베이스 쿼리를 만들어야 합니다.

먼저 데이터베이스에서 찾은 모든 엔티티를 정렬하는 방법을 알아보겠습니다.

Entites 정렬

데이터베이스에서 찾은 모든 엔티티를 정렬하려면 다음 방법 중 하나를 사용할 수 있습니다:

첫째, CrudRepository 인터페이스를 확장하여 저장소 인터페이스를 만든 경우 PagingAndSortingRepository 인터페이스만 확장하도록 수정할 수 있습니다.

저장소 인터페이스의 관련 부분은 다음과 같습니다:
``
interface TodoRepository extends PagingAndSortingRepository<Todo, Long> {

}
``

PagingAndSorting Repository 인터페이스는 데이터베이스에서 찾은 모든 엔티티를 가져와 정렬할 때 사용할 수 있는 한 가지 방법을 선언합니다:

Iterable<T.> findAll(Sort sort) 메서드는 데이터베이스에서 찾은 모든 엔터티를 반환하고 정렬 개체에서 지정한 정렬 옵션을 사용하여 정렬합니다.

즉, 데이터베이스에서 찾은 모든 엔티티의 정렬된 목록을 가져오려면 참을 수 있는 방법 대신 Iterable<T.> findAll(Sort sort) 메서드를 찾습니다.

둘째, Repository 인터페이스를 확장하여 저장소 인터페이스를 만든 경우 저장소 인터페이스에서 findAll(소트 정렬) 메서드를 선언할 수 있습니다.

저장소 인터페이스의 관련 부분은 다음과 같습니다:

``
interface TodoRepository extends Repository<Todo, Long> {

void delete(Todo deleted);

List<Todo> findAll(Sort sort);

Optional<Todo> findOne(Long id);

void flush();

Todo save(Todo persisted);

}
``
We can now get a sorted of list of all entities found from the database by invoking the findAll() method and passing the Sort object as a method parameter.

Sorting the Query Results of Queries That Use the Query Generation From the Method Name Strategy
If we create our database queries from the method name of our query method, we can sort the query results by adding a new method parameter (Sort object) to the query method.

``
interface TodoRepository extends Repository<Todo, Long> {

List<Todo> findByDescriptionContainsOrTitleContainsAllIgnoreCase(String descriptionPart,
                                                                 String titlePart,
                                                                 Sort sort);

}
``

@Query 주석을 사용하는 JPQL 쿼리의 쿼리 결과 정렬

JPQL과 @Query 주석을 사용하여 데이터베이스 쿼리를 생성하는 경우 쿼리 메서드에 새 메서드 매개 변수(Sort 개체)를 추가하여 쿼리 결과를 정렬할 수 있습니다.

@Query 주석을 사용하여 기본 쿼리를 만드는 경우 정렬 클래스를 사용하여 쿼리 결과를 정렬할 수 없습니다. SQL 조회에 정렬 논리를 추가해야 합니다.

예제 응용 프로그램의 검색 기능은 대소문자를 구분하지 않습니다. 제목이나 설명에 지정된 검색어가 포함된 작업관리 항목으로 돌아갑니다. 쿼리 방법이 @Query 주석을 사용하는 경우 소스 코드는 다음과 같습니다:

``
interface TodoRepository extends Repository<Todo, Long> {

@Query("SELECT t FROM Todo t WHERE " +
        "LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
        "LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))")
List<Todo> findBySearchTerm(@Param("searchTerm") String searchTerm, Sort sort);

}
``

JPA 기준 쿼리의 쿼리 결과 정렬

JPA Criteria API를 사용하여 데이터베이스 쿼리를 생성하는 경우 리포지토리 인터페이스가 JpaSpecificationExecutor<T.> 인터페이스를 확장해야합니다.
이 인터페이스는 JPA criteria 쿼리의 쿼리 결과를 정렬할 때 사용할 수 있는 한 가지 방법을 선언합니다:

List<T.> findAll(Specification<T.> spec, Sort sort) 메서드는 Specification 개체가 지정한 조건을 충족하는 모든 엔티티를 반환합니다. 메서드 매개 변수로 제공된 정렬 개체를 사용하여 반환된 엔터티를 정렬합니다.
즉, 목록을 사용하여 JPA 기준 쿼리의 쿼리 결과를 정렬할 수 있습니다
List<T.> findAll(Specification<T.> spec, Sort sort)

리포지토리의 소스 코드정렬 개체를 사용하여 쿼리 결과를 정렬하는 작업관리 검색 서비스 클래스는 다음과 같습니다:

Sorting Query Results of Querydsl Queries

If we create our database queries by using Querydsl, our repository interface must extend the QueryDslPredicateExecutor<T.> interface. This interface declares one method that we can use when we want to sort the query results of the invoked query:

The Iterable<T.> findAll(Predicate predicate, OrderSpecifier<>... orders)
method returns all entities that fulfil the search conditions specified by the Predicate object and sorts the query results by using the sort options specified by the OrderSpecifier objects.

Querydsl로 생성한 쿼리의 결과를 정렬하는 단계

  1. Specify the sorting options by creating new OrderSpecifier objects.

  2. Invoke the findAll() method, and pass the Predicate and OrderSpecier objects as method parameters.

For example, if we want to modify the findBySearchTerm() method of the RepositoryTodoSearchService class to sort the query results in ascending order by using the value of the title field, we have to make following changes to the RepositoryTodoSearchService class:

  1. Add a private orderByTitleAsc() method to the class and implement by returning an OrderSpecifier object which specifies that the search results are sorted in ascending order by using the value of the title field.

  2. Make the following changes to the findBySearchTerm() method:

2.1 Get the OrderSpecifier object by invoking the orderByTitleAsc() method.

2.2 Invoke the Iterable<T.> findAll(Predicate predicate, OrderSpecifier<>... orders) method of the QueryDslPredicateExecutor interface instead of the Iterable<T.> findAll(Predicate predicate) method. Pass the Predicate and OrderSpecifier objects as method parameters.

``
@Service
final class RepositoryTodoSearchService implements TodoSearchService {

private final TodoRepository repository;

@Autowired
public RepositoryTodoSearchService(TodoRepository repository) {
    this.repository = repository;
}

@Transactional(readOnly = true)
@Override
public List<TodoDTO> findBySearchTerm(String searchTerm) {
    Predicate searchPred = titleOrDescriptionContainsIgnoreCase(searchTerm);
    OrderSpecifier sortSpec = orderByTitleAsc();
    Iterable<Todo> searchResults = repository.findAll(searchPred, sortSpec);
    return TodoMapper.mapEntitiesIntoDTOs(searchResults);
}
 
private OrderSpecifier<String> orderByTitleAsc() {
    return QTodo.todo.title.asc();
}

}
``

어느 Method를 사용해 정렬?

@Query, Criteria, Querydsl로 db query를 생성할 때 특정 sorting method를 써야만 한다.

If our database queries are named queries or native queries that use the @Query annotation, we must add the sorting logic into our query strings.

If we create our database queries by using the JPA Criteria API, we have to sort the query results by using the Sort class.

If we create our database queries by using Querydsl, we have to sort the query results by using the OrderSpecifier class.

sort logic과 generation logic을 같이 두는 경우

Querydsl안 써서 특정 method 써야하는 경우 아니면 가독성을 높이기 위해 logic을 같은 위치에 둘 수 있습니다.

If we create our database queries by using SQL or JPQL, we should add the sorting logic into our query strings.

If we create our database queries by using the query generation from the method name strategy, we should use the same method for sorting our query results (append the OrderBy keyword to the method name). If we don't want to use this method because the method name of our query method becomes too long, we should rewrite our query by using the @Query annotation.

sort logic과 query generation logic을 분리해야하는 경우

같은 위치에 두는 것이 가독성이 좋지만 분리해야하는 경우가 있습니다.

If we have to paginate the query results of our database queries, we must sort them by using the Sort class. We will talk more about this in the next part of this tutorial.

If we must support dynamic sorting (i.e. the sorting direction and the used fields can be changed), we must sort our query results by using the Sort class because it is the only way that allows us to fulfil this requirement.

profile
https://github.com/jyzayu

0개의 댓글