springframework/Spring Data JPA

Spring Data Common : Repository

Jungsoomin :) 2020. 11. 15. 18:05

  • 리포지토리 인터페이스를 만들 때 사용하던 JpaRepository 는 Spring Data JPA 의 영역이다.
  • JpaRepositoryPagingAndSortingRepository 를 상속하고 있으며 이 부터 Spring Data Common  영역이다.
  • PagingAndSortingRepository 는 페이징 기능과 소팅 기능을 제공하며 CrudRepsitory를 상속하고 있다.
  • CrudRepository 는 기본적인 CRUD 기능을 정의하고 있으며 Repository 를 상속하고 있다.
  • Repository 인터페이스는 마커 인터페이스이며 실질적 기능은 없고 마커용일 뿐이다.

Repository 인터페이스를 제외하고 CrudRepository 부터는 @NoRepositoryBean 이 붙어있으며, 해당 인터페이스가 실제 Repository 가 아님을 선언해놓은 것이다.

/**
 * JPA specific extension of {@link org.springframework.data.repository.Repository}.
 *
 * @author Oliver Gierke
 * @author Christoph Strobl
 * @author Mark Paluch
 */
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#findAll()
	 */
	@Override
	List<T> findAll();
    ...

 


@DataJpaTest 가 선언되어 있으며, 인 메모리 데이터베이스인 h2 의존이 있을 때 테스트시 인메모리 데이터베이스가 실행되며, 사용 중인 DB 에는 영향을 미치지 않게된다.

 

 <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
</dependency>

 

@RunWith(SpringRunner.class)
@DataJpaTest
public class PostRepositoryTest {
...
}

  • @Rollback 없이 테스트를 진행하면 insert 작업이 일어나지 않고 시퀀스 값만 가져와 테스트를 끝낸다. 
  • 이는 @DataJpaTest 에 붙어있는 @Transactional 때문인데, Test 코드에 @Transational 이 붙으면 모든 테스트는 자동으로 롤백된다.
  • JPA가 롤백될 테스트에 대해서 insert 할 필요성이 없다고 판단하여 insert 작업을 실행하지 않은 것이다.
  • 롤백을 진행하지 않기 위해서는@Rollback 어노테이션에 false 값을 주면 되겠다.
@RunWith(SpringRunner.class)
@DataJpaTest
public class PostRepositoryTest {

    @Autowired
    private PostRepository postRepository;

    @Test
    @Rollback(false)
    public void crud(){
        //Given
        Post post = new Post();
        post.setTitle("Hello Spring Boot Common");
        assertThat(post.getId()).isNull(); // Transient 상태

        //When
        Post newPost = postRepository.save(post);
        //Then
        assertThat(newPost.getId()).isNotNull();
        }
 
 }
Hibernate: 
    call next value for hibernate_sequence

PagingAndSortingRepository 의 기능

  • Iterable<T> findAll(Sort sort) 
  • Page<T> findAll(Pageable pageable) 
  • 오버 로딩이 되어있으나, 주로 Pageable 을 사용하게 된다.
  • PostgresQL 기준으로 limit 를 사용하여 페이징을 위한 쿼리를 만든다.
@RunWith(SpringRunner.class)
@DataJpaTest
public class PostRepositoryTest {

    @Autowired
    private PostRepository postRepository;

    @Test
    @Rollback(false)
    public void crud(){
        //Given
        Post post = new Post();
        post.setTitle("Hello Spring Boot Common");
        assertThat(post.getId()).isNull(); // Transient 상태

        //PagingAndSortingRepository
        //When
        Page<Post> page = postRepository.findAll(PageRequest.of(0, 10));
        //Then
        assertThat(page.getTotalElements()).isEqualTo(1);
        assertThat(page.getNumber()).isEqualTo(0);
        assertThat(page.getSize()).isEqualTo(10);
        assertThat(page.getNumberOfElements()).isEqualTo(1);
        }
        
 }

 

 

  • Pageable 은 펙토리 메서드로 of 를 제공하며 (페이지, 사이즈) 의 매개값을 갖는다.
  • Page<T> 는 유용한 메서드를 제공하는데, getNumber() = 페이지번호, getTotalElement() = 원소개수, getSize() = 요청한 사이즈, getNumberOfElements() = 원소의 개수 등이다. getContent() 는 List<T> 로 데이터를 반환시켜준다.
Hibernate: 
    select
        post0_.id as id1_2_,
        post0_.title as title2_2_ 
    from
        post post0_ limit ?
2020-11-15 17:44:54.104 TRACE 15240 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_] : [BIGINT]) - [1]

제시되어있는 명령어를 입력하여 JPA 로 하여금 원하는 쿼리를 생성하게 할 수도 있다.

public interface PostRepository extends JpaRepository<Post,Long> {

    // 메서드 이름을 분석하여 쿼리를 만들어주는 기능
    Page<Post> findByTitleContains(String title, Pageable pageable);

    Long countByTitleContains(String title);
}

 

@RunWith(SpringRunner.class)
@DataJpaTest
public class PostRepositoryTest {

    @Autowired
    private PostRepository postRepository;

    @Test
    @Rollback(false)
    public void crud(){
        //Given
        Post post = new Post();
        post.setTitle("Hello Spring Boot Common");
        assertThat(post.getId()).isNull(); // Transient 상태
        
        //When
        page = postRepository.findByTitleContains("Spring", PageRequest.of(0, 10));
        //Then
        assertThat(page.getTotalElements()).isEqualTo(1);
        assertThat(page.getNumber()).isEqualTo(0);
        assertThat(page.getSize()).isEqualTo(10);
        assertThat(page.getNumberOfElements()).isEqualTo(1);

        //When
        Long count = postRepository.countByTitleContains("Spring");
        //Then
        assertThat(count).isEqualTo(1);
    }
}

Like 연산자 / count 함수를 사용한 모습

Hibernate: 
    select
        post0_.id as id1_2_,
        post0_.title as title2_2_ 
    from
        post post0_ 
    where
        post0_.title like ? escape ? limit ?
2020-11-15 17:44:54.172 TRACE 15240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [%Spring%]
2020-11-15 17:44:54.173 TRACE 15240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [CHAR] - [\]
2020-11-15 17:44:54.174 TRACE 15240 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_2_] : [BIGINT]) - [1]


Hibernate: 
    select
        count(post0_.id) as col_0_0_ 
    from
        post post0_ 
    where
        post0_.title like ? escape ?
2020-11-15 17:44:54.199 TRACE 15240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [%Spring%]
2020-11-15 17:44:54.208 TRACE 15240 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [CHAR] - [\]
2020-11-15 17:44:54.277 TRACE 15240 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([col_0_0_] : [BIGINT]) - [1]