JAVA/spring

[1028 from 스프링 백기선 JPA] 스프링데이터 Common _ 쿼리, 비동기, 도메인, QueryDSL (추후 추가. )

쿼리 만들기 스프링 데이터 저장소의 메소드 이름으로 쿼리 만드는 방법
메소드 이름을 분석해서 쿼리 만들기 (CREATE)
미리 정의해 둔 쿼리 찾아 사용하기 (USE_DECLARED_QUERY)
미리 정의한 쿼리 찾아보고 없으면 만들기 (CREATE_IF_NOT_FOUND)  >> 기본값

@SpringBootApplication
//@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE)
@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)
//@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.USE_DECLARED_QUERY)
public class DiaryApplication {

리턴타입 
{접두어}{도입부}By{프로퍼티 표현식}(조건식)[(And|Or){프로퍼티 표현식}(조건식)]{정렬 조건} (매개변수)

 
리턴타입 E, Optional<E>, List<E>, Page<E>, Slice<E> Stream<E>
{접두어} Find, Get, Query, Count
{도입부} Distinct, First(N), Top(N)
By  
{프로퍼티 표현식} Person Address ZipCode => findPersonByAddress_ZipCode()
{조건식} IgnoreCase, Between, LessThan, GreaterThan, Like, Contains
[(And|Or)]  
{프로퍼티 표현식}  
{조건식}  
{정렬조건} OrderBy프로퍼티Asc|Desc
(매개변수) Pageable, solt
 
쿼리 찾는 방법 메소드 이름으로 쿼리를 표현하기 힘든 경우에 사용.
저장소 기술에 따라 다름.
JPA: @Query @NamedQuery
@Query  @Query(SELECT c FROM Comment AS C" nativeQuery=true)
@Async 비동기 쿼리
@Async Future<User> findByFirstname(String firstname);  
@Async CompletableFuture<User> findOneByFirstname(String firstname);
@Async ListenableFuture<User> findOneByLastname(String lastname); 
- 해당 메소드를 스프링 TaskExecutor에 전달해서 별도의 쓰레드에서 실행함.
- Reactive랑은 다른 것임

권장하지 않는 이유
테스트 코드 작성이 어려움.
코드 복잡도 증가.
성능상 이득이 없음. 
DB 부하는 결국 같고.
메인 쓰레드 대신 백드라운드 쓰레드가 일하는 정도의 차이.
단, 백그라운드로 실행하고 결과를 받을 필요가 없는 작업이라면 @Async를 사용해서 응답 속도를 향상 시킬 수는 있다.

커스텀 리포지토리 쿼리 메소드(쿼리 생성과 쿼리 찾아쓰기)로 해결이 되지 않는 경우 직접 코딩으로 구현 가능.
스프링 데이터 리포지토리 인터페이스에 기능 추가.
스프링 데이터 리포지토리 기본 기능 덮어쓰기 가능.
구현 방법
커스텀 리포지토리 인터페이스 정의 
인터페이스 구현 클래스 만들기 (기본 접미어는 Impl)
엔티티 리포지토리에 커스텀 리포지토리 인터페이스 추가

기능 추가하기

기본 기능 덮어쓰기

접미어 설정하기
기본 리포지토리 커스터마이징 모든 리포지토리에 공통적으로 추가하고 싶은 기능이 있거나 덮어쓰고 싶은 기본 기능이 있다면 

JpaRepository를 상속 받는 인터페이스 정의
@NoRepositoryBean
기본 구현체를 상속 받는 커스텀 구현체 만들기
@EnableJpaRepositories에 설정
repositoryBaseClass

@NoRepositoryBean public interface MyRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { boolean contains(T entity); }
도메인 이벤트 도메인 관련 이벤트를 발생시키기

스프링 프레임워크의 이벤트 관련 기능
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#context-functionality-events
ApplicationContext extends ApplicationEventPublisher
이벤트: extends ApplicationEvent
리스너
  - implements ApplicationListener  E extends ApplicationEvent
  - @EventListener

스프링 데이터의 도메인 이벤트 Publisher
-@DomainEvents
-@AfterDomainEventPublication
-extends AbstractAggregateRoot<E>
-현재는 save() 할 때만 발생 합니다.

QueryDSL  

Distinct 분명한 : 중복없이 불러옴.

alt F7 사용된 위치 탐색

ctrl + alt + B  자세하게 추적. 

Pageable -> paging 으로 받아야 누락없음. 

             -> sorting 관련 파라메터도 넣을수 있음. 

resolveQuery 처리 : 쿼리 > 프로시져 > 네임드 쿼리 

like sql 키워드  변수 사용불가. 혹은 별명처리.. 

더보기
public final class JpaQueryLookupStrategy {

중략
        protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) {
            RepositoryQuery query = JpaQueryFactory.INSTANCE.fromQueryAnnotation(method, em, this.evaluationContextProvider);
            if (null != query) {
                return query;
            } else {
                RepositoryQuery query = JpaQueryFactory.INSTANCE.fromProcedureAnnotation(method, em);
                if (null != query) {
                    return query;
                } else {
                    String name = method.getNamedQueryName();
                    if (namedQueries.hasQuery(name)) {
                        return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name), this.evaluationContextProvider);
                    } else {
                        RepositoryQuery query = NamedQuery.lookupFrom(method, em);
                        if (null != query) {
                            return query;
                        } else {
                            throw new IllegalStateException(String.format("Did neither find a NamedQuery nor an annotated query for method %s!", method));
                        }
                    }
                }
            }
        }
    }

쿼리관련 참고

더보기

기본 예제

 

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<User> readAllByFirstnameNotNull();

  • try-with-resource 사용할 것. (Stream을 다 쓴다음에 close() 해야 함)