JPA의 나머지 기능

강한친구·2022년 8월 10일
0

JPA

목록 보기
21/27

사실 잘 안쓰는 그런 기술들이다.

복잡도 대비 실무효용성은 별로 없는편이다.

Specification 명세

참 또는 거짓으로 평가
AND OR 같은 연산자로 조합해서 다양한 검색조건을 쉽게 생성(컴포지트 패턴) 예) 검색 조건 하나하나
스프링 데이터 JPA는 org.springframework.data.jpa.domain.Specification 클래스로 정의

Specification은 JPA Criteria를 이용하는데 Criteria는 실무에서 사용하기 너무 불편하고 어려운 기술이다.

실무에서는 대부분 QueryDSL을 쓴다.

QueryByExample

@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이 내부조인만 가능해서 외부조인은 안된다.

  • 중첩 제약 조건이 안되고 매칭조건도 너무 단순하다

Projections

Closed Projections

엔티티대신 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가 이를 구현체로 만들어서 알아서 실행시켜준다.

Open Projections

public interface UsernameOnly {
 @Value("#{target.username + ' ' + target.age + ' ' + target.team.name}")
 String getUsername();
}

엔티티를 전부 가지고 와서 엔티티 안에서 꺼내는 기능이다.

Class Based Projections

인테퍼이스가 아닌 구체적인 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만 사용하는것이 좋다.

동적 네이티브 쿼리

  1. 하이버네이트 직접 활용
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();}
  1. myBatis, JDBCTemplate, jooq 같은 외부 라이브러리 사용

0개의 댓글