나머지 기능들
아래부터는 실무에서 잘 사용하지는 않지만, 알고 있으면 가끔 편리한 기능
- Specifications
- Query By Example
- Projections
- 네이티브 쿼리
Specifications (명세)
책 도메인 주도 설계(Domain Driven Design)는 SPECIFICATION(명세)라는 개념을 소개
스프링 데이터 JPA는 JPA Criteria를 활용해서 이 개념을 사용할 수 있도록 지원
*JPA Criteria: 가독성이 낮음. 실무에 부적합
데이터베이스 언어와 무관하게 쿼리 조건(where, and, or, not)을 추상화해서 조립
JpaSpecificationExecutor 인터페이스를 상속 받아 사용
Specification(인터페이스) 구현체을 파라미터로 받아서 쿼리 수행
참고: 실무에서는 JPA Criteria를 거의 안쓴다! 대신에 QueryDSL을 사용하자.
Query By Example
실제 도메인 객체(Probe)를 이용하여 쿼리 생성
엔티티 필드와 쿼리 조건을 매칭하여 사용한다.
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을 사용하자
Projections
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
엔티티 대신에 DTO를 편리하게 조회할 때 사용
전체 엔티티가 아니라 만약 회원 이름만 딱 조회하고 싶으면?
단순히 이야기하면, select 절에 특정 필드만 포함 (ex. DTO 초기화를 위한 필드만 조회)
public interface MemberRepository ... {
List<UsernameOnly> findProjectionsByUsername(String username);
} //메서드 이름은 자유, 반환 타입으로 인지
인터페이스 기반 Close projection
조회할 엔티티의 필드를 getter 형식으로 지정하면 해당 필드만 선택해서 조회(Projection)
public interface UsernameOnly {
String getUsername();
}
스프링 데이터 JPA에 의해 UsernameOnly(인터페이스)를 구현한 프록시 객체가 담겨서 반환된다.
인터페이스 기반 Open projection
스프링의 SpEL 문법도 지원. SpEL문법을 사용하면, DB에서 엔티티 필드를 다 조회한 후 @Value 값 계산
따라서 JPQL SELECT 절 최적화 X
public interface UsernameOnly {
@Value("#{target.username + ' ' + target.age + ' ' + target.team.name}")
String getUsername();
}
클래스 기반 Projection
인터페이스가 아닌 구체적인 DTO 형식도 가능. 생성자의 파라미터 이름으로 필드 매칭
프록시가 아닌 구체 클래스로 반환
public class UsernameOnlyDto {
private final String username;
public UsernameOnlyDto(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
동적 Projections
Generic type을 주면, 동적으로 프로젝션 데이터 번경 가능
<T> List<T> findProjectionsByUsername(String username, Class type);
사용코드
List<UsernameOnly> result = memberRepository.findProjectionsByUsername("m1", UsernameOnly.class);
중첩 구조 처리
연관관계 엔티티(DTO 속 DTO) -> 인터페이스 속 인터페이스
public interface NestedClosedProjection {
String getUsername();
TeamInfo getTeam();
interface TeamInfo {
String getName();
}
}
변환된 SQL:
select m.username as col_0_0_, t.teamid as col_1_0_, t.teamid as teamid1_2_, t.name as name2_2_
from member m
left outer join team t on m.teamid=t.teamid
where m.username=?
=> root 엔티티(Member)는 최적화, 중첩된 엔티티(Team)은 모든 필드를 조회
주의
프로젝션 대상이 root 엔티티면, JPQL SELECT 절 최적화 가능
프로젝션 대상이 ROOT가 아니면
-LEFT OUTER JOIN 처리
-모든 필드를 SELECT해서 엔티티로 조회한 다음에 계산
정리
프로젝션 대상이 root 엔티티면 유용하다.
프로젝션 대상이 root 엔티티를 넘어가면 JPQL SELECT 최적화가 안된다!
실무의 복잡한 쿼리를 해결하기에는 한계가 있다.
실무에서는 단순할 때만 사용하고, 조금만 복잡해지면 QueryDSL을 사용하자
네이티브 쿼리(SQL)
가급적 네이티브 쿼리는 사용하지 않는게 좋음, 정말 어쩔 수 없을 때 사용
최근에 나온 궁극의 방법 -> 스프링 데이터 Projections 활용
스프링 데이터 JPA 기반 네이티브 쿼리
@Query(value = "select * from member where username = ?", nativeQuery = true)
Member findByNativeQuery(String username);
통상적으로 엔티티보단 DTO로 조회하고 싶을 때 사용한다. 또는 통계성 쿼리
그러나 아래와 같은 제약이 있기 때문에, 네이티브 SQL을 DTO로 조회할 때는 리포지토리를 직접 구현하거나 커스텀 리포지토리를 통해 JdbcTemplate or myBatis 사용 권장
페이징 지원
반환 타입: Object[], Tuple, DTO(스프링 데이터 인터페이스 Projections 지원)
제약
-Sort 파라미터를 통한 정렬이 정상 동작하지 않을 수 있음(믿지 말고 직접 처리)
-JPQL처럼 애플리케이션 로딩 시점에 문법 확인 불가
-동적 쿼리 불가
Projections 활용 (정적 쿼리)
예) 스프링 데이터 JPA 네이티브 쿼리 + 인터페이스 기반 Projections 활용
@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);
select 절에서 뽑아낸 필드를 MemberProjection 구현체에 대입하고 반환
-MemberProjection: 인터페이스
-반환타입 Page<T>: 네이티브 countQuery 추가 기재 필요
동적 네이티브
쿼리 하이버네이트를 직접 활용
스프링 JdbcTemplate, myBatis, jooq같은 외부 라이브러리 사용
예) 하이버네이트 기능 사용
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")
.setResultTransformer(Transformers.aliasToBean(MemberDto.class))
.getResultList();'Advance I > Spring Data JPA' 카테고리의 다른 글
| 22.07.04 (0) | 2022.07.04 |
|---|---|
| 22.07.02 (0) | 2022.07.02 |
| 22.07.01 (0) | 2022.07.01 |
| 22.06.30 (0) | 2022.06.30 |
| 22.06.28 :: 실전! 스프링 데이터 JPA (0) | 2022.06.30 |