JAN's History

JPA기초 다지기_JPA 객체 지향 쿼리 본문

JPA

JPA기초 다지기_JPA 객체 지향 쿼리

JANNNNNN 2024. 3. 26. 11:59

JPQL소개 (JPA + SQL)

  • 가장 단순한 조회 방법
  • EntityManager.find()
  • 객체 그래프 탐색을 할 수 있어 엔티티 객체를 중심으로 개발 가능
  • 문제는 검색 쿼리인데, 테이블이 아닌 엔티티 객체를 대상으로 검색
  • 그러나 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요
  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
  • SQL과 문법 유사
    • SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
  • JPQL은 엔티티 객체를 대상으로 쿼리 vs SQL은 데이터베이스 테이블을 대상으로 쿼리
  • SQL을 추상화해서 특정 데이터베이스 SQL에 의존 X
  • JPQL을 한마디로 정의하면 객체 지향 SQL
import static org.assertj.core.api.Assertions.*;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class JpqlTest {

    @DisplayName("JQPL 테스트")
    @Test
    void jpqlTest() {
        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        final EntityManager em = emf.createEntityManager();
        final EntityTransaction transaction = em.getTransaction();

        transaction.begin();

        try {
            final Member memberA = new Member(1L, null, "Member A");
            em.persist(memberA); // 영속 상태
            final Member memberB = new Member(2L, null, "Member B");
            em.persist(memberB);
            final Member memberC = new Member(3L, null, "C");
            em.persist(memberC);

            em.flush();
            em.clear();

            final String jpql = "SELECT m FROM Member m WHERE m.name LIKE '%Member%'";
            final List<Member> members = em.createQuery(jpql, Member.class)
                    .getResultList();

            assertThat(members).hasSize(2);

            transaction.commit();
        } catch (final Exception e) {
            System.out.println(e.getMessage());
            transaction.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}

  • 엔티티와 속성은 대소문자 구분(Member, username)
  • JPQL 키워드는 대소문자 구분 안함
  • 테이블 이름이 아닌 엔티티 이름을 사용해야 함.
  • 별칭은 필수

결과조회 API

  • query.getResultList(): 결과가 하나 이상, 리스트 반환
  • query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환(정확히 하나가 아니면 예외 발생)

파라미터 바인딩 - 이름 기준, 위치 기준

SELECT m
FROM Member m
WHERE m.username = :username query.setParameter("username", usernameParam);

SELECT m
FROM Member m
WHERE m.username = ?1 query.setParameter(1, usernameParam);

페이징 API

  • JPA는 페이징을 다음 두 API로 추상화
  • setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
  • setMaxResult(int maxResult): 조회할 데이터 수
import static org.assertj.core.api.Assertions.*;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.transaction.Transactional;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@Transactional
class JpqlTest {
    @DisplayName("페이징 테스트")
    @Test
    void pagingTest() {
        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        final EntityManager em = emf.createEntityManager();
        final EntityTransaction transaction = em.getTransaction();

        transaction.begin();

        try {

            for (long i = 0; i < 40; i++) {
                em.persist(new Member(i, null, "Member " + i));
            }

            em.flush();
            em.clear();

            final String jpql = "SELECT m FROM Member m ORDER BY m.name DESC";
            final List<Member> members = em.createQuery(jpql, Member.class)
                    .setFirstResult(0)
                    .setMaxResults(20)
                    .getResultList();

            assertThat(members).hasSize(20);

            transaction.commit();
        } catch (final Exception e) {
            System.out.println(e.getMessage());
            transaction.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}
select member0_.id      as id1_0_,
       member0_.name    as name2_0_,
       member0_.team_id as team_id3_0_
from Member member0_
order by member0_.name DESC limit ?

페이징 API - SQL / Oracle 방언

집합과 정렬

SELECT COUNT(m), // 수
    SUM (m.age), // 합
    AVG (m.age), // 평균
    MAX (m.age), // 최대
    MIN (m.age) // 최소
from
    Member m;
  • GROUP BY, HAVING
  • ORDER BY

조인

// 내부 조인
SELECT m
FROM Member m [INNER] JOIN m.team t;

// 외부 조인
SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t;

// 세타 조인
SELECT count(m)
FROM Member m,
     Team t
WHERE m.username = t.name;

페치 조인 (현업에서 자주 사용)

  • 엔티티 객체 그래프를 한번에 조회하는 방법
  • N+1의 문제를 해결할 수 있어서 유용함!
// JPQL
SELECT m
FROM Member m
         join fetch m.team;

// SQL
SELECT M.*, T.*
FROM MEMBER M
         INNER JOIN TEAM T ON M.TEAM_ID = T.ID;
import static org.assertj.core.api.Assertions.*;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.transaction.Transactional;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@Transactional
class JpqlTest {
    @DisplayName("페치조인 테스트")
    @Test
    void fetchJoinTest() {
        final EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        final EntityManager em = emf.createEntityManager();
        final EntityTransaction transaction = em.getTransaction();

        transaction.begin();

        try {
            final Team teamA = new Team(1L, "TEAM A");
            final Team teamB = new Team(2L, "TEAM B");
            em.persist(teamA);
            em.persist(teamB);
            final Member memberA = new Member(1L, teamA, "MEMBER A");
            final Member memberB = new Member(2L, teamB, "MEMBER B");

            em.persist(memberA);
            em.persist(memberB);

            em.flush();
            em.clear();

            final String jpql = "SELECT m FROM Member m JOIN FETCH m.team";
            final List<Member> members = em.createQuery(jpql, Member.class)
                    .getResultList();

            assertThat(members).hasSize(2);

            transaction.commit();
        } catch (final Exception e) {
            System.out.println(e.getMessage());
            transaction.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}