스프링데이터 + JPA/스프링 데이터 JPA

29. 네이티브 쿼리

sdafdq 2023. 11. 26. 21:47

먼저 사실 Jpa를 사용하면 네이티브 쿼리는 왠만하면 사용하지 않는 게 좋음.

정말 정말 방법이 없을 때 최종적으로 사용하는 게 네이티브 쿼리. (리포지토리가 DB끼리의 표준화가 안될 터이니..)

 

일단 그래도 정말 가끔 어쩔 수 없어서 짜야 할 수도 있으니, 알 긴 해야할 듯.

 

방법은 정말 간단함.

그냥 Spring Data Jpa에서

@Query(value = "select * from member where username = ?", nativeQuery = true)
Member findByNativeQuery(String username);

jpql 직접 할 때 처럼 저렇게 @Query하고 쿼리 적는 건 똑같은데, 아마 jpql과 문법이 달라 문자열이지만 오류를 표시해 줌. 그 때 nativeQuery를 true로 해 주면 아, 저거 native 쿼리구나, 하고 오류를 더이상 표시하지 않음.

근데 네이티브 SQL 쿼리 오류는 지원 안해줌.

 

@Test
public void nativeQuery(){
    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();

    Member result = memberRepository.findByNativeQuery("m1");
    System.out.println("result = " + result.getUsername());
}

이렇게 실행 해 보면

 

select
    * 
from
    member 
where
    username = ?

저 쿼리 그대로 나감.

select * from member where username = 'm1';

?에 인자 바인딩 잘 되서 나갔음.

 

result = m1

결과도 잘 나옴.

 

 

근데 아무래도 좀 제약적인 부분이 있음. 물론 좋은 점도 있음.

 

좋은 점은

페이징이 됨

 

반환타입 같은 경우는

오브젝트 배열,

튜플

우리가 만든 Dto 

 

제약은 Sort 파라미터를 통한 정렬이 정상동작하지 않을 수 있음. 그냥 쿼리로 넣으라는 뜻 인듯.

JPQL처럼 애플리케이션 로딩 시점에 문법 검증이 안됨.

동적 쿼리가 불가함.

 

네이티브 쿼리 쓰고 싶으면 간단한 경우에는 저렇게 쓰고, 보통 jdbc템플릿 쓰는 게 좋나봄.

 

일단 근데 좋은점이 저 Dto, 저번 시간에 배운 Projection을 통해서 정말 간단하게 할 수 있음.

먼저 프로젝션용? 그러니까 Dto용 인터페이스를 만들거임.

public interface MemberProjection {
    Long getId();
    String getUsername();
    String getTeamName();
}

정말 간단하게.

 

여기서 이제

@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);

이렇게. 보면 as 해서 가져오는 이름을 별칭으로 프로퍼티에 맞추고 있음. 프로젝션이 클래스라면 생성자의 인자 이름에 맞추면 될 듯.

이건 이렇게 하면 join도 가능함.

그리고 이거 페이징으로 할거라, 보면 count쿼리는 아무래도 네이티브 쿼리이니 직접 짜셈.

nativeQuery true 해주고,

페이지로 받을거고, 프로젝션 객체로, PageRequest를 인자로 받을거임.

 

@Test
public void nativeQuery(){
    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();

    Page<MemberProjection> result = memberRepository.findByNativeProjection(PageRequest.of(0, 10));
    List<MemberProjection> content = result.getContent();

    for (MemberProjection memberProjection : content) {
        System.out.println("memberProjection = " + memberProjection.getUsername());
        System.out.println("memberProjection = " + memberProjection.getTeamName());
    }
}

보면 저렇게 페이지로 받으면서, PageRequest.of(0, 10) 하면서 간단하게 Pageable 객체 만들어주면서 인자로 넣어줬음.

static 클래스.of가 뭔가 정보를 주면서 그걸 객체로 만들어 반환해주는 패턴인 듯.

여튼 잘 나옴.

 

정적 쿼리는 이렇게 가능함. 좀 그냥 간단하게 하고 싶을 때. 페이징도 되고.

 

 

 

그럼 동적 네이티브 쿼리는?

Jdbc 템플릿 같은 외부 라이브러리 사용하셈.

 

// 대충 동적 쿼리 완성되었다 치고
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();
}

왜 우리가 커스텀 인터페이스 만들고, 그거 구현한 스프링데이터JPA이름 + Impl 에다가 자동으로 구현되어지는 메소드 말고 직접 구현한 메소드 집어 넣었었잖아. 그렇게 하면 됨.