ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 6. 나머지 기능들
    Spring-Boot/실전! 스프링 데이터 JPA 2023. 2. 24. 10:43

    실무에서 쓰기는 애매한 기술들.

    1. Specifications (명세) - 실무에서 쓰지마라!

    - 책 도메인 주도 설계(Domain Driven Design) 는 SPECIFICATION(명세) 라는 개념을 소개

    - 스프링 데이터 JPA는 JPA Criteria를 활용해서 이 개념을 사용할 수 있도록 지원

     

    * 술어(predicate) *

    - 참 또는 거짓으로 평가

    - AND OR 같은 연산자로 조합해서 다양한 검색조건을 쉽게 생성(컴포넌트 패턴)

    - 예) 검색 조건 하나하나

    - 스프링 데이터 JPA는 `org.springframework.data.jpa.domain.Specification` 클래스로 정의

     

    * 명세 기능 사용 방법 *

    * `JpaSpecificationExecutor` 인터페이스 상속 *

     

    - `Specification`을 구현하면 명세들을 조립할 수 있음. `where()`, `and()`, `or()`, `not()` 제공

    - `findAll` 을 보면 회원 이름 명세(`username`)와 팀 이름 명세(`teamName`) 를 `and` 로 조합해서 검색 조건으로 사용

    - 명세를 정의하려면 `Specfication` 인터페이스를 구현

    - 명세를 정의할 때는 `toPredicate(...)` 메서드만 구현하면 되는 JPA Criteria의 `Root`, `CriteriaQuery`, `CriteriaBuilder` 클래스를 파라미터 제공

     

    * 참고 : 실무에서는 JPA Criteria를 거의 안쓴다! 대신에 QueryDSL을 사용하자. *

     

    2. Query By Example

    // when
    // Probe 생성
    Member member = new Member("m1");
    Team team = new Team("teamA");
    member.setTeam(team);
    
    ExampleMatcher matcher = ExampleMatcher.matching().withIgnorePaths("age");
    Example<Member> example = Example.of(member, matcher);
    List<Member> result = memberRepository.findAll(example);
    
    Assertions.assertThat(result.get(0).getUsername()).isEqualTo("m1");

    - Probe : 필드에 데이터가 있는 실제 도메인 객체

    - ExampleMatcher : 특정 필드를 일치시키는 상세한 정보 제공, 재사용 가능

    - Example : Probe와 ExampleMatcher로 구성, 쿼리를 생성하는데 사용

     

    * 장점 *

    - 동적 쿼리를 편리하게 처리

    - 도메인 객체를 그대로 사용

    - 데이터 저장소를 RDB에서 NOSQL로 변경해도 코드 변경이 없게 추상화 되어 있음

    - 스프링 데이터 JPA `JpaRepository` 인터페이스에 이미 포함

     

    * 단점 *

    - 조인은 가능하지만 내부 조인(INNER JOIN)만 가능함 외부 조인(LEFT JOIN) 안됨

    - 다음과 같은 중첩 제약조건 안됨

        ㆍ`firstname = ?0 or (firstname = ?1 and lastname = ?2)`

    - 매칭 조건이 매우 단순함

        ㆍ문자는 `starts/contains/ends/regex`

        ㆍ다른 속성은 정확한 매칭(`=`)만 지원

     

    * 정리 *

    - * 실무에서 사용하기에는 매칭 조건이 너무 단순하고, LEFT 조인이 안됨 *

    - * 실무에서는 QueryDSL을 사용하자 *

     

    3. Projections

    - 엔티티 대신에 DTO를 편리하게 조회할 때 사용

    - 전체 엔티티가 아니라 만약 회원 이름만 딱 조회하고 싶으면?

     

    ```java
    public interface UsernameOnly {
        String getUsername();
    }

    - 조회할 엔티티의 필드를 getter 형식으로 지정하면 해당 필드만 선택해서 조회 (Projection)

     

    ```java
    public interface MemberRepository {
        List<UsernameOnly> findProjectionsByUsername(String username);
    }
    ```

    - 메서드 이름은 자유, 반환 타입으로 인지

     

    * 주의 *

    - 프로젝션 대상이 root 엔티티면, JPQL SELECT 절 최적화 가능

    - 프로젝션 대상이 ROOT가 아니면

        ㆍLEFT OUTER JOIN 처리

        ㆍ모든 필드를 SELECT해서 엔티티로 조회한 다음에 계산

     

    * 정리 *

    - * 프로젝션 대상이 root 엔티티면 유용하다. *

    - * 프로젝션 대상이 root 엔티티를 넘어가면 JPQL SELECT 최적화가 안된다! *

    - * 실무의 복잡한 쿼리를 해결하기에는 한계가 있다. *

    - * 실무에서는 단순할 때만 사용하고, 조금만 복잡해지면 QueryDSL을 사용하자 *

     

    4. 네이티브 쿼리

    - 가급적 네이티브 쿼리는 사용하지 않는게 좋음, 정말 어쩔 수 없을 때 사용

    - 최근에 나온 궁극의 방법 → 스프링 데이터 Projections 활용

     

    * 스프링 데이터 JPA 기반 네이티브 쿼리 *

    - 페이징 지원

    - 반환 타입

        ㆍObject[]

        ㆍTuple

        ㆍDTO(스프링 데이터 인터페이스 Projections 지원)

    - 제약

        ㆍSort 파라미터를 통해 정렬이 정상 동작하지 않을 수 있음(믿지 말고 직접 처리)

        ㆍJPQL 처럼 애플리케이션 로딩 시점에 문법 확인 불가

        ㆍ동적 쿼리 불가

     

    * JPA 네이티브 SQL 지원 *

    ```java
    public interface MemberRepository extends JpaRespository<Member, Long> {
        @Query(value = "select * from member where username = ?", nativeQuery = true)
        Member findByNativeQuery(String username);
    }
    ```

    - JPQL은 위치 기반 파라미터를 1부터 시작하지만 네이티브 SQL은 0부터 시작

    - 네이티브 SQL을 엔티티가 아닌 DTO로 변환은 하려면

        ㆍDTO 대신 JPA TUPLE 조회

        ㆍDTO 대신 MAP 조회

        ㆍ@SqlResultSetMapping → 복잡

        ㆍHibernate ResultTransformer를 사용해야함 → 복잡

        ㆍ* 네이티브 SQL을 DTO로 조회할 때는 JdbcTemplate or myBatis 권장 *

     

    * Projections 활용 *

    - 예) 스프링 데이터 JPA 네이티브 쿼리 + 인터페이스 기반 Projections 활용

    ```java
    @Query(value = "SELECT m.member_id as id, m.username, t.name as teamName " +
                "FROM member m left join team t"
            countQuery = "SELECT count(*) from member",
            nativeQuery = true)
    Page<MemberProjection> findByNativeProjection(Pageable pageable);

     

    동적 네이티브 쿼리

    - 하이버네이트를 직접 활용

    - 스프링 JdbcTemplate, myBatis, jooq 같은 외부 라이브러리 사용

    ```java
    // given
    String sql = "select m.username as username from member m";
    
    List<MemberDto> result = em.createNativeQuery(sql)
                .setFirstResult(0)
                .setMaxResults(10)
                .unwrap(NativeQuery.class)
                .addScalar("username")
                .setResultTransfromer(Transformers.aliasToBean(MemberDto.class)
                .getResultList();

    댓글

Designed by Tistory.