JPA

[JPA] 객체지향 쿼리 언어 - 1

리승자이 2022. 8. 5. 23:34
728x90

JPA는 복잡한 검색 조건을 사용해서 엔티티 객체를 조회할 수 있는 다양한 쿼리기술을 지원한다.

JPQL은 가장 중요한 객체지향 쿼리 언어이다. 다른 Criteria나 QueryDSL은 JPQL을 편리하게 사용하도록 도와주는 기술이므로 JPA를 다루는 개발자라면 JPQL을 필수로 학습해야 된다.

객체지향 쿼리 소개

EntityManager.find() 메소드를 사용하면 식별자로 엔티티 하나를 조회할 수 있다.
검색 방법은 두가지다.

  • 식별자 조회 : EntityManager.find();
  • 객체 그래프 탐색 : a.getB().getC();
    이런 단순한 SELECT 쿼리들로는 개발을 하지 않는다.
    복잡 쿼리나 통계형 쿼리같은 복잡한 쿼리들을 수행할 때에는 이런 쿼리로는 택도 없다.
    ORM을 사용하면 DB테이블이 아니라 엔티티 객체를 대상으로 개발하기 때문에 검색도 마찬가지로 엔티티 객체를 대상으로 하는 방법이 필요함.
    JPQL이 이 문제를 해결하기 위해 만들어졌다. 특징은 다음과 같다.
  • 테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리
  • SQL을 추상화해서 특정 DB SQL에 의존하지 않는다.

JPQL을 사용하게 되면 JPA는 이 JPQL을 분석한 다음 적절한 SQL을 만들어 DB를 조회한다. 그리고 그 결과로 엔티티 객체를 생성하여 반환해준다.

JPQL은 객체지향 SQL이라고 정의할 수 있다.

JPA 공식 지원 기능

  • JPQL
  • Criteria 쿼리 : JPQL을 편하게 작성하도록 도와주는 API, Builder 클래스 모음
  • 네이티브 SQL : Mybatis처럼 JPQL대신 직접 SQL사용하는 기능
  • QueryDSL : Criteria 쿼리처럼 JPQL을 편하게 작성하도록 도와주는 빌더 클래스 모음, 비표준 오픈소스 프레임워크임.
  • JDBC 직접 사용, SQL 매퍼 프레임워크 사용 : 필요에 의해 JDBC를 직접 사용가능.

@Test
void jpqlTest() {
    Member member = new Member("kim");
    em.persist(member);
    em.flush();

    String jpql = "select m from Member as m where m.name = 'kim'";
    List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();

    assertThat(resultList.get(0).getName()).isEqualTo("kim");
}

실행결과는 다음과 같다.

다른 값들은 생성자에서 설정 안해주었기 때문에 넣지 않았다.
근데 조금 다른 것이 있다.
그것은 원래 우리가 사용하던 방식인 em.find()를 사용했을 때에는 영속성 컨텍스트인 member가 지금 존재하기 때문에 SELECT쿼리를 날리지 않고 1차캐시에서 조회했었다.

그렇지만 JPQL은 직접 SQL을 추상화해서 사용하기 때문에 쿼리를 날려서 조회하게 된다. 그래서 항상 DB에서 찾는데 이 찾은 엔티티가 영속성 컨텍스트에 존재한다면? DB에서 찾는 데이터를 버리고 영속성 컨텍스트에 있는 반환값을 넘긴다

Criteria의 장점은 문자가 아닌 query.select(m).where(...) 처럼 메소드 체이닝으로 JPQL을 작성할 수 있다.
장점이 많지만 모든 장점을 상쇄할 정도로 복잡하고 장황하다. 따라서 사용하기 불편한건 물론이고 Criteria로 작성한 코드도 한눈에 들어오지 않는 단점이 있음.

@Test
void criteriaTest() {
    //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("name"), "kim"));
    List<Member> resultList = em.createQuery(cq).getResultList();        
}

QueryDSL

QueryDSL도 Criteria처럼 JPQL 빌더 역할을 한다. QueryDSL의 장점은 코드 기반이면서 단순하고 사용하기 쉽다. 작성한 코드도 JPQL과 비슷해서 한눈에 들어온다.

QueryDSL은 JPA 표준은 아니고 오픈소스 프로젝트이다.

코드를 보자

//세팅
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;

//쿼리, 결과조회
List<Member> members = query.from(member)
                            .where(member.name.eq("kim"))
                            .list(member);

여기서 QMember 클래스는 Member 엔티티 클래스를 기반으로 생성한 QueryDSL 쿼리 전용 클래스이다.

여기까지 보면 이제는 JPA와 QueryDSL로 전반적인 코드를 작성할 수 있을것 같다.
그리고 추가적으로 정말 복잡한 것이라면 네이티브 쿼리를 사용하는것도 방법이겠다.

네이티브 쿼리

SQL을 직접 사용하는 기능이 바로 네이티브 쿼리이다. SI회사에 근무했을 당시 MyBatis, iBatis 많이 사용했는데 바꾸려는 시도가 최근에 이루어졌었기 때문에 점점 JPA가 편해져 가는중이지만 그래도 익숙해보인다.

  • 단점
    • 특정 DB에 의존하는 SQL을 작성해야 한다는 것. 그래서 DB를 바꾸면 쿼리문도 수정해주어야 한다.

내가 MySQL이나 MariaDB를 배웠는데 회사에서 Oracle, PostgreSQL 사용한다면 바꿔야 한다는 얘기이다...

String sql = "SELECT id, name, age FROM Member WHERE name='kim'";
List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();

JDBC를 사용하는것과 흡사하다. 다른점은 다음에서 설명하겠다.

JDBC 직접 사용, MyBatis 같은 SQL Mapper 사용

Hibernate에서 JDBC 커넥션을 획득하는 방법

Session session = em.unwrap(Session.class);
session.doWork(new Work() {

    @Override
    public void execute(Connection connection) throws SQLException {
       //작업..
    }
});

위에서 다른점이라고 하는것이 이부분인데,
JDBC나 MyBatis를 JPA와 함께 사용하면 영속성 컨텍스트 관리가 안되는 애들이기 때문에 컨텍스트를 적절한 시점에 강제로 플러시 해줘야 한다. 영속성 컨텍스트와 DB 불일치로 데이터 무결성을 훼손해선 절대적으로 안된다.

728x90