본문 바로가기

전체보기/Spring

[JPA] JpaRepository vs CrudRepository(1) - Paging과 Sorting / QueryExampleExecutor

최근 JPA관련 글들을 보던 중,

JpaRepository를 사용하는 예제를 보게되었습니다.

 

저는 지금까지 CrudRepository만을 사용하고 있었기 때문에,

JpaRepository와 CrudRepository는 무엇이 다른지 궁금해졌고

 

이번 글에서는 그 과정에서 알게된 내용들을 정리해보려 합니다.

 

이번 글의 예제들은 다음과 같은 환경에서 작성되었습니다.

  • spring-boot-starter-data-jpa:2.6.0 
  • MySQL 8.0

우선 JpaRepository, CrudRepository를 포함한 class diagram를 살펴보면,

 

 

JpaRepository는 CrudRepository에 PaingAndSortingRepository, QueryByExampleExecutor를

상속하고 있는 것을 알 수 있습니다.

 

그럼 PaingAndSortingRepository, QueryByExampleExecutor이 각각 어떻게 정의되어 있는지 살펴보겠습니다.

PagingAndSortingRepository.java

더보기
/**
 * Extension of {@link CrudRepository} to provide additional methods to retrieve entities using the pagination and
 * sorting abstraction.
 *
 * @author Oliver Gierke
 * @see Sort
 * @see Pageable
 * @see Page
 */
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

	/**
	 * Returns all entities sorted by the given options.
	 *
	 * @param sort
	 * @return all entities sorted by the given options
	 */
	Iterable<T> findAll(Sort sort);

	/**
	 * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
	 *
	 * @param pageable
	 * @return a page of entities
	 */
	Page<T> findAll(Pageable pageable);
}

 

PaingAndSortingRepository는 이름에서 알 수 있듯이,

paging과 sorting 기능을 기본적으로 제공하고 있습니다.

 

코드로 한 번 살펴보겠습니다.

 

@Repository
public interface PlayerJpaRepository extends JpaRepository<Player, BigInteger> {
}

 

우선, JpaRepository를 상속받은 repository와

 

@Repository
public interface PlayerCrudRepository extends CrudRepository<Player, BigInteger> {
	Page<Player> findAll(Pageable pageable);
}

 

CrudRepository를 상속받은 repository를 정의하였습니다.

 

JpaRepository를 상속받은 repository에는 이미 paging 기능이 제공되기 때문에

별도의 method를 정의할 필요가 없지만,

CrudRepository에는 paging에 필요한 값을 전달할 수 있는 Pageable 객체를 받는 method를 정의합니다.

 

위 두 repository를 사용하는 코드는 다음과 같습니다.

 

PageRequest pageRequest = PageRequest.of(0, 1000);

Page<Player> jpaResult = jpaRepository.findAll(pageRequest);

Page<Player> crudResult = crudRepository.findAll(pageRequest);

 

paging을 위한 시작 index와 size값을 PageRequest 객체로 전달하고

동일한 method를 호출하면, 다음과 같이 query가 실행됩니다.

 

#JpaRepository 실행 query
select 
	player0_.id as id1_0_, 
	player0_.age as age2_0_, 
	player0_.birthday as birthday3_0_, 
	player0_.name as name4_0_ 
from player player0_ 
limit ?

#CrudRepository 실행 query
select 
	player0_.id as id1_0_, 
	player0_.age as age2_0_, 
	player0_.birthday as birthday3_0_, 
	player0_.name as name4_0_ 
from player player0_ 
limit ?

 

보시는 것처럼 query는 JpaRepository와 CrudRepository에서 차이가 없는 것을 확인할 수 있습니다.

 

그렇다면 sorting은 어떻게 구현할 수 있을까요?

 

@Repository
public interface PlayerJpaRepository extends JpaRepository<Player, BigInteger> {
}

 

여기서도 JpaRepository를 상속할 경우에는 별도의 method를 정의하지 않지만

 

@Repository
public interface PlayerCrudRepository extends CrudRepository<Player, BigInteger> {
	List<Player> findAll(Sort sort);
}

 

CrudRepository를 상속받을 경우, sorting 규칙을 전달할 수 있는

Sort 객체를 파라미터로 전달받는 method를 정의합니다.

 

그리고 다음 코드와 같이 위 두 method를 호출하고 수행된 query를 확인하면,

 

Sort sort = Sort.by(Sort.Direction.DESC, "birthday");

List<Player> jpaResult = jpaRepository.findAll(sort);

List<Player> crudResult = crudRepository.findAll(sort);

 

#JpaRepository 실행 query
select 
	player0_.id as id1_0_,
	player0_.age as age2_0_,
	player0_.birthday as birthday3_0_,
	player0_.name as name4_0_ 
from player player0_ 
order by player0_.birthday desc

#CrudRepository 실행 query
select 
	player0_.id as id1_0_,
	player0_.age as age2_0_,
	player0_.birthday as birthday3_0_,
	player0_.name as name4_0_ 
from player player0_ 
order by player0_.birthday desc

 

역시나 JpaRepositoy, CrudRepository 모두 동일한 query가 실행되는 것을 확인할 수 있습니다.

 

지금까지 살펴본 내용을 정리하면,

JpaRepository의 경우 paging 및 sorting이 필요할 때,

모든 entity를 조회할 때에 한하여 별도의 method를 정의할 필요가 없다는 것이

CrudRepository와 다른 점이라고 볼 수 있을 것 같습니다.

 

하지만 개인적으로, '실제 모든 데이터를 아무런 조건도 없이 가져올 일이 있을까?'라는 의문이 남습니다.

 

JpaRepository 역시 특정 조건에서 paging, sorting을 하려면

다음과 같이 별도의 method를 정의해 주어야 합니다.

 

@Repository
public interface PlayerJpaRepository extends JpaRepository<Player, BigInteger> {
	Page<Player> findAllByName(String name, Pageable pageable);
}

 

그럼 JpaRepository가 상속하는 또 하나의 interface인

QeuryByExampleExecutor에 대해서 알아보겠습니다.

 

QeuryByExampleExecutor.java

더보기
/**
 * Interface to allow execution of Query by Example {@link Example} instances.
 *
 * @param <T>
 * @author Mark Paluch
 * @author Christoph Strobl
 * @since 1.12
 */
public interface QueryByExampleExecutor<T> {

	/**
	 * Returns a single entity matching the given {@link Example} or {@literal null} if none was found.
	 *
	 * @param example must not be {@literal null}.
	 * @return a single entity matching the given {@link Example} or {@link Optional#empty()} if none was found.
	 * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if the Example yields more than one result.
	 */
	<S extends T> Optional<S> findOne(Example<S> example);

	/**
	 * Returns all entities matching the given {@link Example}. In case no match could be found an empty {@link Iterable}
	 * is returned.
	 *
	 * @param example must not be {@literal null}.
	 * @return all entities matching the given {@link Example}.
	 */
	<S extends T> Iterable<S> findAll(Example<S> example);

	/**
	 * Returns all entities matching the given {@link Example} applying the given {@link Sort}. In case no match could be
	 * found an empty {@link Iterable} is returned.
	 *
	 * @param example must not be {@literal null}.
	 * @param sort the {@link Sort} specification to sort the results by, must not be {@literal null}.
	 * @return all entities matching the given {@link Example}.
	 * @since 1.10
	 */
	<S extends T> Iterable<S> findAll(Example<S> example, Sort sort);

	/**
	 * Returns a {@link Page} of entities matching the given {@link Example}. In case no match could be found, an empty
	 * {@link Page} is returned.
	 *
	 * @param example must not be {@literal null}.
	 * @param pageable can be {@literal null}.
	 * @return a {@link Page} of entities matching the given {@link Example}.
	 */
	<S extends T> Page<S> findAll(Example<S> example, Pageable pageable);

	/**
	 * Returns the number of instances matching the given {@link Example}.
	 *
	 * @param example the {@link Example} to count instances for. Must not be {@literal null}.
	 * @return the number of instances matching the {@link Example}.
	 */
	<S extends T> long count(Example<S> example);

	/**
	 * Checks whether the data store contains elements that match the given {@link Example}.
	 *
	 * @param example the {@link Example} to use for the existence check. Must not be {@literal null}.
	 * @return {@literal true} if the data store contains elements that match the given {@link Example}.
	 */
	<S extends T> boolean exists(Example<S> example);
}

 

QueryExampleExecutor는 원하는 조건에 해당하는 Entity를 생성하고 

이를 전달함으로써 원하는 값을 조회할 수 있게 해줍니다.

 

바로 코드를 살펴보면,

 

@Repository
public interface PlayerJpaRepository extends JpaRepository<Player, BigInteger> {
}

 

@Repository
public interface PlayerCrudRepository extends CrudRepository<Player, BigInteger> {
	List<Player> findAll(Example<Player> example);
}

 

이번에도 JpaRepository를 상속받는 repository는 비어있는 상태로,

CrudRepository를 상속받는 repository는 Example 객체를 전달받는 method를 정의합니다.

 

그리고 아래와 같이 두 method를 호출하고 query 실행 내역을 확인하면

 

Player player = new Player();
player.setName("Phillip Molina");

Example<Player> example = Example.of(player);

List<Player> jpaResult = jpaRepository.findAll(example);

List<Player> crudResult = crudRepository.findAll(example);
#JpaRepository 실행 query
select 
	player0_.id as id1_0_,
	player0_.age as age2_0_,
	player0_.birthday as birthday3_0_,
	player0_.name as name4_0_ 
from 
	player player0_ 
where
	player0_.name=?

#CrudRepository 실행 query
select 
	player0_.id as id1_0_,
	player0_.age as age2_0_,
	player0_.birthday as birthday3_0_,
	player0_.name as name4_0_ 
from 
	player player0_ 
where
	player0_.name=?

 

위와 같이 query가 수행되는 것을 확인할 수 있습니다.

 

눈치채신 분들도 계시겠지만,

Example 객체를 전달할때 원하는 조건에 해당하는 값(위 예제에서는 name 값)만

채워주면 해당 field만을 활용하여 query를 생성하여 실행합니다.

 

그럼 여기까지 PaingAndSortingRepository, QueryByExampleExecutor를 상속하면서 오는,

JpaRepository와 CrudRepository의 차이에 대한 설명이었습니다. 

 

다음글에서는 JpaRepository 자체에 구현된 차이점에 대해서 정리해보도록 하겠습니다.

Reference

https://www.baeldung.com/spring-data-query-by-example

 

 

반응형