스프링 데이터 JPA-09.쿼리 만들기 개요 및 실습,비동기 쿼리(스프링 데이터 Common)


스프링 데이터 Common: 쿼리 만들기 개요

스프링 데이터 저장소의 메소드 이름으로 쿼리 만드는 방법

  • 메소드 이름을 분석해서 쿼리 만들기 (CREATE)
  • 미리 정의해 둔 쿼리 찾아 사용하기 (USE_DECLARED_QUERY)
  • 미리 정의한 쿼리 찾아보고 없으면 만들기 (CREATE_IF_NOT_FOUND)
@SpringBootApplication
@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND) // 기본값
//@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE)
//@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.USE_DECLARED_QUERY)
public class JpaApplication {

	public static void main(String[] args) {
		SpringApplication.run(JpaApplication.class, args);
	}
}

쿼리 만드는 방법

  • 리턴타입 {접두어}{도입부}By{프로퍼티 표현식}(조건식)[(AndOr){프로퍼티 표현식}(조건식)]{정렬 조건} (매개변수)
구분내용
접두어Find, Get, Query, Count, …
도입부Distinct, First(N), Top(N)
프로퍼티 표현식Person.Address.ZipCode => find(Person)ByAddress_ZipCode(…)
조건식IgnoreCase, Between, LessThan, GreaterThan, Like, Contains, …
정렬 조건OrderBy{프로퍼티}Asc | Desc
리턴 타입E, Optional, List, Page, Slice, Stream
매개변수Pageable, Sort

쿼리 찾는 방법

  • 메소드 이름으로 쿼리를 표현하기 힘든 경우에 사용.
  • 저장소 기술에 따라 다름.
  • JPA: @Query @NamedQuery

스프링 데이터 Common: 쿼리 만들기 실습

기본 예제

List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// distinct
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// ignoring case
List<Person> findByLastnameIgnoreCase(String lastname);
// ignoring case
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

정렬

List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);

페이징

Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);

스트리밍

Stream readAllByFirstnameNotNull();

  • try-with-resource 사용할 것. (Stream을 다 쓴다음에 close() 해야 함)
public interface CommentRepository extends MyRepository<Comment, Long> {

    List<Comment> findByCommentContains(String keyword);
    List<Comment> findByCommentContainsAndLikeCountAfter(String comment, int likeCount);
    List<Comment> findByCommentContainsAndLikeCountGreaterThanEqual(String comment, int likeCount);
    List<Comment> findByCommentContainsIgnoreCaseOrderByLikeCountDesc(String comment);

    Page<Comment> findByCommentContainsIgnoreCase(String comment, Pageable pageable);
    Stream<Comment> findByCommentContainsOrderByLikeCountDesc(String comment, Pageable pageable);

    @Test
    public void crud() throws ExecutionException, InterruptedException {

        createComment("spring data jpa", 30);
        createComment("spring web mvc", 40);
        createComment("spring rest api", 50);

        // spring 포함
        List<Comment> list = commentRepository.findByCommentContains("spring");
        assertEquals(list.size(), 3);
        // 40 초과
        list = commentRepository.findByCommentContainsAndLikeCountAfter("spring", 40);
        assertEquals(list.size(), 1);
        // 40 이상
        list = commentRepository.findByCommentContainsAndLikeCountGreaterThanEqual("spring", 40);
        assertEquals(list.size(), 2);
        // 정렬 및 대소문자(IgnoreCase)
        list = commentRepository.findByCommentContainsIgnoreCaseOrderByLikeCountDesc("SprinG");
        assertEquals(list.size(), 3);
        assertEquals(list.get(0).getLikeCount(), 50);

        // 페이징
        PageRequest pageRequest = PageRequest.of(0, 10, Sort.Direction.DESC, "LikeCount");
        Page<Comment> pageList = commentRepository.findByCommentContainsIgnoreCase("spring", pageRequest);
        assertEquals(pageList.getNumberOfElements(), 3);
        assertEquals(pageList.getTotalPages(), 1);

        // stream
        try(Stream<Comment> comments = commentRepository.findByCommentContainsOrderByLikeCountDesc("spring", pageRequest)){
            Comment firstComment = comments.findFirst().get();
            assertEquals(firstComment.getLikeCount(), 50);
        }
    }

스프링 데이터 Common: 비동기 쿼리

비동기 쿼리

  • @Async Future findByFirstname(String firstname);
  • @Async CompletableFuture findOneByFirstname(String firstname);
  • @Async ListenableFuture findOneByLastname(String lastname);
    • 해당 메소드를 스프링 TaskExecutor에 전달해서 별도의 쓰레드에서 실행함.
    • Reactive랑은 다른 것임

권장하지 않는 이유

  • 테스트 코드 작성이 어려움.
  • 코드 복잡도 증가.
  • 성능상 이득이 없음.
    • DB 부하는 결국 같고.
    • 메인 쓰레드 대신 백드라운드 쓰레드가 일하는 정도의 차이.
    • 단, 백그라운드로 실행하고 결과를 받을 필요가 없는 작업이라면 @Async를 사용해서 응답 속도를 향상 시킬 수는 있다.
    @Async
    //Future<List<Comment>> findByCommentContains(String comment, Pageable pageable);
    ListenableFuture<List<Comment>> findByCommentContains(String comment, Pageable pageable);

        // 권장하지 않음, 사용하려면 애플리케이션에 @EnableAsync 붙여줘야한다.
        // 이 코드가 다른 스레드로 떨어져 있다고 생각.
        // 기존 데이터는 select 가능하나, 여기서 지금 조작중인 데이터는 감지하지 못한다.
        // Future<List<Comment>> future = commentRepository.findByCommentContains("spring", pageRequest);
        ListenableFuture<List<Comment>> future = commentRepository.findByCommentContains("spring", pageRequest);
        System.out.println("==============");
        System.out.println("is done? " + future.isDone());

        future.addCallback(new ListenableFutureCallback<List<Comment>>() {
            @Override
            public void onFailure(Throwable throwable) {
                System.out.println("fail");
                System.out.println(throwable);
            }

            @Override
            public void onSuccess(@Nullable List<Comment> comments) {
                System.out.println("onSuccess");
                System.out.println(comments.size()); // 0 만 조회됨.
                // 이 트랜잭선 이전의 데이터 조회. 변화 감지 못함 (위에서 insert 한 것 감지못함)
            }
        });

        List<Comment> futureList = future.get();
        futureList.forEach(System.out::println); // 출력안됨
    }

쉽게 다시 설명하자면,
선언한 비동기 쿼리는 다른 스레드
즉, 지금 저 테스트에서 도는 메인 스레드와 별개로 다른 스레드가 처리한다.

이 다른 스레드가 비동기 쿼리를 처리할 때

  • 단순히 기존에 존재하던 데이터의 조회를 하는 것이라면 상관이 없다.
  • 그러나 현재 메인 스레드에서 데이터를 조작한다고 했을때, 이 변화를 감지하지 못한다.

사용한 테스트 코드를 참조하면

  • 시작점에서 데이터를 3건 insert 해주지만, 이걸 감지하지 못하고 0건만 나온다.
  • 왜냐햐면 비동기 쿼리(저기 해당하는 코드)는 메소드 시작부터 이미 다른 곳에 가 있다고 생각하고, 처음부터 다른 스레드가 처리한다고 생각하면 편하다.
  • 시점이 저 메서드를 시작하기 이전으로 봐야 한다. (저 메소드가 시작하기 전에는 데이터가 없었다. 그래서 0건 출력됨)





© 2019. by jaeuk