사실 잘 안쓰는 그런 기술들이다.
복잡도 대비 실무효용성은 별로 없는편이다.
참 또는 거짓으로 평가
AND OR 같은 연산자로 조합해서 다양한 검색조건을 쉽게 생성(컴포지트 패턴) 예) 검색 조건 하나하나
스프링 데이터 JPA는 org.springframework.data.jpa.domain.Specification 클래스로 정의
Specification은 JPA Criteria를 이용하는데 Criteria는 실무에서 사용하기 너무 불편하고 어려운 기술이다.
실무에서는 대부분 QueryDSL을 쓴다.
@Test
public void basic() throws Exception {
//given
Team teamA = new Team("teamA");
em.persist(teamA);
em.persist(new Member("m1", 0, teamA));
em.persist(new Member("m2", 0, teamA));
em.flush();
//when
//Probe 생성
Member member = new Member("m1");
Team team = new Team("teamA"); //내부조인으로 teamA 가능
member.setTeam(team);
//ExampleMatcher 생성, age 프로퍼티는 무시
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("age");
Example<Member> example = Example.of(member, matcher);
List<Member> result = memberRepository.findAll(example);
//then
assertThat(result.size()).isEqualTo(1);
멤버 엔티티 자체를 검색인자로 사용해서 example로 만들고 이를 넘겨서 검색하는 기능이다.
Example은 Probe, ExampleMatcher로 구성되어있고, 쿼리를 생성한다.
동적쿼리를 편하게 처리
도메인 객체를 그대로 사용한다
RDB에서 NOSQL로의 전환이 자유롭다
JPARepository에 이미 들어가 있다.
Join이 내부조인만 가능해서 외부조인은 안된다.
중첩 제약 조건이 안되고 매칭조건도 너무 단순하다
엔티티대신 DTO를 조회하고 싶을때 사용한다.
예를 들어, 전체 회원이 아닌 회원 이름만 조회할 때 사용한다.
적용방법은 다음과 같다.
우선 인터페이스를 만든다.
public interface UserNameOnly {
String getUserName();
}
그리고 이를 찾아오는 함수를 repository안에 만든다.
List<UsernameOnly> findProjectionsByUsername(String username);
단순히 interfaceDto를 반환하는 검색형을 만들어내면 된다.
@Test
public void projections() throws Exception {
//given
Team teamA = new Team("teamA");
em.persist(teamA);
Member m1 = new Member("m1", 0, teamA);
Member m2 = new Member("m2", 0, teamA);
em.persist(m1);
em.persist(m2);
em.flush();
em.clear();
//when
List<UsernameOnly> result =
memberRepository.findProjectionsByUsername("m1");
//then
Assertions.assertThat(result.size()).isEqualTo(1);
}
이렇게 테스트를 돌려보면 잘 작동한다.
인터페이스를 정의하면, JPA가 이를 구현체로 만들어서 알아서 실행시켜준다.
public interface UsernameOnly {
@Value("#{target.username + ' ' + target.age + ' ' + target.team.name}")
String getUsername();
}
엔티티를 전부 가지고 와서 엔티티 안에서 꺼내는 기능이다.
인테퍼이스가 아닌 구체적인 DTO형식도 가능하다.
생성자의 파라미터 이름으로 매칭한다.
private final String username;
public UsernameOnlyDto(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public interface NestedClosedProjections {
String getUsername();
TeamInfo getTeam();
interface TeamInfo {
String getName();
}
}
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=?
중첩구조에서는 첫번째 항목에 대해서는 최적화를 해서 이름만 가지고 올 수 있지만, 두번째 항목(Team) 부터는 최적화에 실패해서 엔티티를 전부 가지고 오는것을 볼 수 있다.
프로젝션 대상이 root 엔티티면 유용하다.
프로젝션 대상이 root 엔티티를 넘어가면 JPQL SELECT 최적화가 안된다 따라서 복잡한 쿼리를 해결하기에는 한계가 있다.
단순할 때만 사용하고, 조금만 복잡해지면 QueryDSL을 사용하자
JPA가 제공하는 기능으로 SQL을 직접 작성하는 기능이다.
@Query(value = "select * from member where username = ?", nativeQuery = true)
Member findByNativeQuery(String username);
이렇게 해서 돌려보면 내가 작성한 저 sql이 그대로 작동하는것을 볼 수 있다.
혹은 프로젝션을 이용해서 페이징처리를 곁들인 네이티브 쿼리를 날릴 수 있다.
@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);
네이티브 쿼리의 반환타입은 여러가지 있는데 다른 타입들은 너무 지저분해서 DTO만 사용하는것이 좋다.
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();}