JPA/JPA 기본

37. JPQL

sdafdq 2023. 10. 30. 07:10

 JPA는 다양한 방식으로 쿼리방법을 지원

JPQL

JPA Criteria

QueryDSL

네이티브 SQL

JDBC API 직접사용, MyBatis, SpringJdbcTemplate와 함께

 

 

JPA Criteria 자바코드로 짜서 JPQL로 빌드해주는 제너레이터

네이티브 SQL은 JPQL써도 가끔 정말 DB종속적인 쿼리를 짜야할 때가 있음. 표준 SQL을 벗어나는.

 

거의 JPQL로 해결이 되긴 하는데, 정말 가끔 안될 때가 있다.

그 때 네이티브SQL이나 MyBatis나 스프링JDBCTemplate 같은 걸 쓰면 된다.

 

JPQL은 엔티티중심적 언어.

테이블은 매핑만 하는 거고, JPQL은 엔티티 대상으로 작성하는 언어임.

 

 

결국 문제는 검색쿼리인데,

JPQL은 검색도 테이블이 아닌 엔티티 객체를 대상으로 검색한다.

 

모든 DB데이터를 객체로 변환해서 검색하는 것은 불가능.

 

애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요.

 

JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공.

 

SQL과 유사하며, select, from, where, group by, having, join 등 ANSI SQL 표준에서 요구하는 건 다 지원함.

 

 

JPQL : 엔티티 객체 대상

SQL : DB 테이블 대상

 

그래도 JPQL을 짜면 결국 SQL로 번역되어 DB로 날리는 거임.

 

List<Member> result = em.createQuery("select m from Member m where m.name like 'kim%'",Member.class).getResultList();

이렇게, em.createQuery() 해서 jpql 만들어서 날려보면(SQL과 거의 유사. 그런데 저 Member는 테이블이 아니라 실제로 엔티티임.), 

Hibernate: 
    /* select
        m 
    from
        Member m 
    where
        m.name like 'kim%' */ select
            m1_0.MEMBER_ID,
            m1_0.locker_id,
            m1_0.USERNAME,
            m1_0.TEAM_ID 
        from
            Member m1_0 
        where
            m1_0.USERNAME like 'kim%' escape ''

이렇게 주석으로 JPQL이 나오고,

그 뒤에 실제로 날려지는 번역되어진 SQL문이 나온다.

 

엔티티 매핑 정보를 읽고, SQL문으로 번역함.

JPQL
select m from Member m where m.name like 'kim%'

SQL
select * from Member where name like 'kim%'

JPQL에서 Member as m 짧게 해서 Member m인데, select 할 때 그 m을 다 가져오니 Member 필드에 매핑된거 다 가져오라고 쿼리.

 

 

JPQL은 SQL을 추상화해서 만든거라 특정 DB의 SQL에 의존하지 않는다.

 

근데, 

select m from Member m where m.name like 'kim%'

이 JPQL도 사실 문자열이다.

그래서 동적쿼리 만들기 좀 그렇다. 뭐 컴파일러가 문법을 잡아줄 수 있는 것 도 아니고.

 

그 중 여러 방법이 있는데(결국 QueryDSL을 쓰게 될 거지만) 그 중 Criteria

이건 자바 표준에서 제공하는 기능.

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);

Root<Member> m = query.from(Member.class);

CriteriaQuery<Member> cq  = query.select(m).where(cb.like(m.get("name"),"kim%"));
em.createQuery(cq).getResultList();

자바코드로 쿼리를 짠다. QueryDSL과 유사한데, QueryDSL이 더 직관적이긴 하다.

CriteriaBuilder를 em으로 부터 얻어오고,

Criteria 전용 Query를 그 빌더로부터 Member.class 엔티티 지정해서 만든다음,

 

Root를 query.from()하면서 루트 엔티티를 선택해 주고,

쿼리를 짜면 된다.

그렇게 짠 쿼리를 

em.createQuery()안에 인자로 넣어주면 된다.

그럼

Hibernate: 
    /* <criteria> */ select
        m1_0.MEMBER_ID,
        m1_0.locker_id,
        m1_0.USERNAME,
        m1_0.TEAM_ID 
    from
        Member m1_0 
    where
        m1_0.USERNAME like ? escape ''

이렇게 날라간다.

m 루트 전체필드들을 가져오는 것,

from Member(엔티티와 연결된),

where like까지 가져왔다.

 

이게 좋은 점이,

동적쿼리 짜기 좋다.

String username = "asdf";
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);

Root<Member> m = query.from(Member.class);

CriteriaQuery<Member> cq  = query.select(m);

if(username != null){
    cq.where(cb.like(m.get("name"),"kim%"));
}

em.createQuery(cq).getResultList();

저렇게, 중간에 끊었다가

이어서 할 수 있다.

 

그래도 QueryDSL을 권장한다. SQL과 비슷하기에, 사용하기 더 직관적이다.

List<Member> list = query.selectFrom(m)
                        .where(m.age.gt(18))
                        .orderBy(m.name.desc())
                        .fetch();

그냥 SQL문을 함수로 옮겨 놓은 듯 한..

List<Member> list = query.select(m)
                        .from(m)
                        .where(m.name.like("kim%"))
                        .fetch();

동적 쿼리도 Criteria 처럼 이어서 할 수 있다.

 

또한 따로 함수로 빼내어 재사용도 가능하다.

 

 

QueryDSL이 실무 사용 권장.

얘도 결국 JPQL을 만들어주는 빌더임.

 

JPQL을 잘 알면 된다.

 

 

 

네이티브 SQL

JPQL로 해결할 수 없는 표준에서 벗어난 특정 DB에 의존적인 문법이나 기능 같은 거.

뭐 근데 이런 거 몇개는 또 하이버네이트가 방언이라고 지원함.

 

여튼 그냥 생쿼리임. 말 그대로 네이티브 쿼리

em.createNativeQuery("select * from member where name like 'kim%'")
                            .getResultList();

해 보면 저거 그대로 나감.

Hibernate: 
    /* dynamic native SQL query */ select
        * 
    from
        member 
    where
        name like 'kim%'

 

 

 

 

 

JDBC 직접 사용, SpringJdbcTemplate 등

@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
    String sql = "update item set item_name = ?, price = ?, quantity = ? where id = ?";
    template.update(sql, updateParam.getItemName(), updateParam.getPrice(), updateParam.getQuantity(),itemId);
}

이런 식. JDBCTemplate

sql에, ? 에 바인딩

근데, 이런 기술 같은 경우는 JPA와 아예 관련이 없기 때문에,

적절한 시기에 강제로 flush() 해 줘야 함. SQL 실행하기 직전 등.

 

JPA 관련 기술들은 em.nativeQuery()도 내부에서 플러시를 호출함. 쿼리 날리는 함수들은.

 

 

보통

95% JPQL, QueryDSL

5% SpringJDBCTemplate로 네이티브 쿼리.

 

복잡한 통계쿼리 같은 건 아예 저 5%, 네이티브 쿼리로 짜기도 한다고.. 근데 또 그런 복잡한 것도 JPQL, QueryDSL로도 한다고.. 

 

 

'JPA > JPA 기본' 카테고리의 다른 글

39. 프로젝션(select)  (0) 2023.10.30
38. JPQL 기본 문법  (0) 2023.10.30
36. 값타입 실전  (0) 2023.10.30
35. 값타입 컬렉션  (0) 2023.10.29
34. 값타입 비교  (0) 2023.10.29