10. 객체지향 쿼리 언어

객체지향 쿼리 소개

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

JPQL

  • 테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리다.
  • SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
String jpql = "SELECT m FROM Member as m WHERE m.username = 'kim'";
List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();

Criteria

  • JPQL을 편하게 작성하도록 도와주는 빌더 클래스로 문자가 아닌 프로그래밍 코드로 JPQL을 작성한다.
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();
장점
  • 컴파일 시점에 오류 발견
  • IDE를 통해 코드 자동완성
  • 동적 쿼리 작성 용이
단점
  • 복잡하고 사용하기 불편함
  • 직관적이지 않음

QueryDSL

  • JPQL을 편하게 작성하도록 도와주는 빌더 클래스로 문자가 아닌 프로그래밍 코드로 JPQL을 작성한다.

  • pom.xml 설정

    // 쿼리타입 생성 플러그인
    <plugin>
      <groupId>com.mysema.maven</groupId>
      <artifactId>apt-maven-plugin</artifactId>
      <version>1.1.3</version>
      <executions>
          <execution>
              <goals>
                  <goal>process</goal>
              </goals>
              <configuration>
                  <outputDirectory>target/querydsl</outputDirectory>
                  <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
              </configuration>
          </execution>
      </executions>
    </plugin>
    
    // 쿼리 타입을 생성할 때 필요한 라이브러리
    <dependency>
      <groupId>com.mysema.querydsl</groupId>
      <artifactId>querydsl-apt</artifactId> 
      <version>3.6.3</version>
      <scope>provided</scope>
    </dependency>
    
    // QueryDSL JPA 라이브러리
    <dependency>
      <groupId>com.mysema.querydsl</groupId>
      <artifactId>querydsl-jpa</artifactId> 
      <version>3.6.3</version>
    </dependency>
    
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member; // 기본 인스턴스 사용

List<Member> members = query.from(member)
                            .where(member.username.eq("kim"))
                            .list(member);
장점
  • 컴파일 시점에 오류 발견
  • IDE를 통해 코드 자동완성
  • 동적 쿼리 작성 용이
  • 단순하고 사용하기 편함
  • 직관적임

Native SQL

  • SQL을 직접 사용할 수 있다.
  • 특정 데이터베이스에 의존하는 기능을 사용할 때 사용한다.
  • 데이터베이스를 변경하면 Native SQL도 수정해야 한다.
String sql = "SELECT id, age, team_id, name FROM Member WHERE name = 'kim'";  
List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();

QueryDSL

결과 조회

  • uniqueResult() : null or 하나의 데이터 반환 or NonUniqueResultException
  • singleResult() : null or 첫 데이터 반환
  • list() : 빈 컬렉션 or 목록 반환

조건 검색

query.from(member)
     .where(member.name.eq("Bob").or(member.name("Jo"))
     .list(member);

// JPQL
select member
from Member member
where member.name = ? or member.name = ?

페이징과 정렬

query.from(item)
     .orderBy(item.price.desc(), item.name.asc())
     .offset(10).limit(10)
     .list(item);

// JPQL
select item
from Item item
order by item.price desc, item.name asc
limit ?, ?

그룹

query.from(item)
     .groupBy(item.price)
     .having(item.price.gt(1000))
     .list(item);

// JPQL
select item
from Item item
group by item.price
having item.price > ?

조인

query.from(order)
     .leftJoin(order.orderItems, orderItem)
     .on(orderItem.price.gt(2000))
     .list(order);

// JPQL
select order 
from Order order left join order.orderItems orderItem
on orderItem.price > ?
페치조인
  • JPQL에서 성능 최적화를 위해 제공
  • 연관된 엔티티나 컬렉션을 한 번에 같이 조회
  • 글로벌 로딩전략 FetchType.LAZY 보다 우선순위라 필요부분에 즉시로딩 가능

엔티티 페치조인 - 연관된 엔티티를 함께 조회

query.from(member)
     .innerJoin(member.team, team).fetch()
     .list(member);

// JPQL
select member
from Member member inner join fetch member.team

컬렉션 페치조인 - 일대다 관계의 컬렉션을 함께 조회

query.from(team)
     .innerJoin(team.members, member).fetch()
     .where(team.name.eq('A'))
     .list(team);

// JPQL
select team
from Team team join fetch team.members
where team.name = ?

서브 쿼리

QItem item = QItem.item; 
QItem i = new QItem("i"); 

query.from(item)
      .where(item.price.eq(
          new JPASubQuery().from(i).unique(i.price.max())
      ))
      .list(item);

// JPQL
select item 
from Item item
where item.price = ( select max(i.price) 
                     from Item i )

동적 쿼리

...
    if (search.isDateSearch()) {
      query.where(partner.createdDt.between(startDate, DateUtil.addDays(endDate, 1)));
    }
    if (StringUtils.isNotBlank(search.getKeyword())) {
      query.where(partner.name.like(search.getKeyword() + "%"));
    }
    if (StringUtils.isNotBlank(search.getCountry())) {
      query.where(partner.countryCode.eq(search.getCountry()));
    }
    if (StringUtils.isNotBlank(search.getStatus())) {
      query.where(partner.status.eq(search.getStatus()));
    }
...

수정/삭제 배치 쿼리

  • 한 번에 여러 데이터 수정/삭제
QItem item = QItem.item;

JPAUpdateClause updateClause = new JPAUpdateClause(em, item);
updateClause.where(item.name.eq("A"))
            .set(item.price, 2000)
            .execute();  

JPADeleteClause deleteClause = new JPADeleteClause(em, item);
deleteClause.where(item.name.eq.("A"))
            .execute();

// JPQL            
update Item item
set item.price = ?
where item.name = ?

delete from Item item 
where item.name = ?

객체지향 쿼리 심화

벌크 연산

  • 영속성 컨텍스트를 무시하고 DB에 직접 쿼리
    • 벌크 연산 직후 em.refresh()
    • 벌크 연산 먼저 실행
    • 벌크 연산 직후 영속성 컨텍스트 초기화

영속성 컨텍스트와 JPQL

  • JPQL은 항상 DB 직접 조회
  • JPQL로 조회한 엔티티는 영속 상태
  • 영속성 컨텍스트에 이미 존재하는 엔티티가 있으면 DB에서 조회한 결과를 버리고 기존 엔티티 반환
  • 영속성 컨텍스트는 영속 상태인 엔티티의 동일성 보장

JPQL과 플러시 모드

  • 플러시 : 영속성 컨텍스트의 변경 내용을 DB에 반영
  • 쿼리에 설정하는 플러시 모드는 엔티티 매니저에 설정하는 플러시 모드보다 우선권 소유
    em.setFlushMode(FlushModeType.AUTO) 커밋orJPQL시 플러시
    em.createQuery(jpql, Member.class).setFlushMode(FlushModeType.COMMIT) 커밋시 플러시

참고

results matching ""

    No results matching ""