공부/JPA

JPA 객체지향 쿼리 언어 - JPQL, QueryDSL 등 소개

데부한 2022. 7. 16. 17:44
반응형

출처 : 자바 ORM 표준 JPA 프로그래밍 인프런 강의

 

JPA 게시글은 대부분 인프런의 김영한님의 강의인 '자바 ORM 표준 JPA 프로그래밍' 기반으로 내용을 정리했습니다.

 

객체지향 쿼리 언어(JPQL)

 

JPA는 다양한 쿼리 방법 지원

  • JPQL
  • JPA Criteria
  • QueryDSL
  • 네이티브 SQL
  • JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용

 

JPQL

  • 가장 단순한 조회 방법
    • EntityManager.find()
    • 객체 그래프 탐색
  • 나이가 18살 이상인 회원을 모두 검색하고 싶다면? 이런 경우에는 em.find()로 가져올 수가 없다.
  • JPA를 사용하면 엔티티 객체를 중심으로 개발
  • 문제는 검색 쿼리
  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요
  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다.
  • SQL과 문법이 유사하며 SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN을 지원한다.(ANSI 표준 전부 지원)
  • JPQL은 엔티티 객체를 대상으로 쿼리한다.
  • SQL은 데이터베이스 테이블을 대상으로 쿼리한다.

 

조회

List<Member> result =  em.createQuery("select m From Member m where m.username like '%kim%'",
        Member.class).getResultList();
  • em.createQuery() 메서드를 이용해 JPQL을 사용할 수 있다.
  • get.ResultList()는 조회해 온 결과를 List로 만들어준다.
  • JPQL은 테이블이 아닌 객체를 대상으로 쿼리한다.

 

// 출력 로그
Hibernate: 
    /* select
        m 
    From
        Member m 
    where
        m.username like '%kim%' */ select
            member0_.MEMBER_ID as MEMBER_I1_6_,
            member0_.city as city2_6_,
            member0_.street as street3_6_,
            member0_.zipcode as zipcode4_6_,
            member0_.USERNAME as USERNAME5_6_,
            member0_.endDate as endDate6_6_,
            member0_.startDate as startDat7_6_ 
        from
            Member member0_ 
        where
            member0_.USERNAME like '%kim%'

 

JPQL을 한마디로 정의하면 객제 지향 SQL이다.

 


 

Criteria

  •  JPQL의 경우 쿼리가 String으로 이루어져있어 동적 쿼리를 만들기 어렵다.
  • 문자가 아닌 자바 코드로 JPQL을 작성할 수 있다.
  • JAVA 표준 문법에서 지원하는 기능이며  JPA 공식 기능이다.
// 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.equal(m.get("username"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
  • Criteria는 동적 쿼리를 쉽게 만들 수 있다.
  • JAVA 표준 문법이라 String 제외 메서드에서 오타가 날 시 에러 표시가 되어 에러를 찾기 쉽다.
  • 하지만 사용하기에 어려움이 있어 실무에서는 잘 사용하지 않는다. 
    • 복잡하여 유지보수가 어렵다.
  • 그래서 대신 QueryDSL 사용을 권장한다.

 

QueryDSL 소개

  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있다.
  • JPQL 빌더 역할
  • 컴파일 시점에 문법 오류를 찾을 수 있다.
  • 동적 쿼리 작성이 편리하다.
  • 단순하고 쉽다.
  • 실무에서 사용하길 권장한다.
//JPQL
// select m from Member m where m.age > 18
JAPFactoryQuery query = new JPAQueryFactory(em);
QMember m = QMember.member;

List<Member> list =
		query.selectFrom(m)
        	 .where(m.age.at(18))
             .orderBy(m.name.desc())
             .fetch();
JPQL을 잘하면 QueryDSL는 쉽게 배울 수 있다.

 


 

네이티브 SQL 소개

  • JPA가 제공하는 SQL을 직접 사용하는 기능
  • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능
    예) 오라클 CONNECT BY, 특정 DB만 사용하는 SQL 힌트
  • 잘 사용하지 않는다.
String query = "select MEMBER_ID, city, street, zipcode, USERNAME from MEMBER";
List<Member> resultList =
        em.createNativeQuery(query).getResultList();

 


 

JDBC 직접 사용, SpringJdbcTemplate 등

  • JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나, 스프링 JdbcTemplate, Mybatis 등을 함께 사용 가능하다.
  • 단 영속성 컨텍스트를 적절한 시점에 강제로 플러시 해줘야한다.
    예) JPA를 우회에서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동 플러시 한다.
Member member = new Member();
member.setUsername("member1");
em.persist(member);

// flush ->commit, query
String query = "select MEMBER_ID, city, street, zipcode, USERNAME from MEMBER";
List<Member> resultList =
        em.createNativeQuery(query, Member.class).getResultList();

for(Member member1 : resultList) {
    System.out.println("member1 = " + member1);
}

tx.commit();

위 코드는 정상적으로 작동되는 코드이다.

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (city, street, zipcode, USERNAME, endDate, startDate, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    /* dynamic native SQL query */ select
        MEMBER_ID,
        city,
        street,
        zipcode,
        USERNAME 
    from
        MEMBER
member1 = hellojpa.Member@5eccd3b9

위 출력 로그를 보면 select 하기 전에 먼저 flush가 된다. 그래야 persist() 한 것을 DB에 반영하고, 그 반영된 데이터 값을 가져올 수 있기 때문이다. 참고로 flush는 commit, query() 시 자동으로 동작한다.

그런데 조회 쿼리를 스프링 DB 커넥션 같은 걸 사용해서 조회를 할 때 문제가 생긴다. DB 커넥션은 JPA에서 관리하지도 않아 조회 전에 flush가 자동으로 동작하지 않아 persist()한 member는 아직 JPA 안에 있어, 제대로 된 데이터를 가져올 수가 없게된다. 그러니 조회 전에 강제로 flush를 날려줘야된다.

 

 

반응형