728x90
JPA 객체지향 쿼리
JPA는 다양한 쿼리 방법을 지원
- JPQL
- JPA Criteria
- QueryDSL
- 네이티브 SQL
- JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용
JPQL 소개
- 가장 단순한 조회 방법
- 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);
프로젝션
// 엔티티 프로젝션
SELECT m
FROM Meber m;
// 엔티티 프로젝션
SELECT m.team
FROM Member m;
// 단순 값 프로젝션
SELECT m.username, m.age
FROM Member m // new 명령어란 단순 값을 DTO로 바로 조회
SELECT new jpabook.jpql.UserDTO(m.username, m.age)
FROM Member m;
- DISTINT는 중복을 제거한다.
페이징 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 - MySQL 방언
페이징 API - 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;
- 하이버네이트 5.1부터 세타 조인도 외부 조인 가능
페치 조인
- 엔티티 객체 그래프를 한번에 조회하는 방법
- 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();
}
}
Hibernate
:
select member0_.id as id1_0_0_,
team1_.id as id1_1_1_,
member0_.name as name2_0_0_,
member0_.team_id as team_id3_0_0_,
team1_.name as name2_1_1_
from Member member0_
inner join
Team team1_ on member0_.team_id = team1_.id
기타
- 서브 쿼리 지원
- EXISTS, IN
- BETWEEN, LIKE, IS NULL
기본 함수
- CONCAT
- SUBSTRING
- TRIM
- LOWER, UPPER
- LENGTH
- LOCATE
- ABS, SQRT, MOD
- SIZE, INDEX(JPA 용도)
CASE 식
// 기본 CASE 식
SELECT CASE
WHEN m.age <= 10 THEN '학생요금'
WHEN m.age >= 60 THEN '경로요금'
ELSE '일반요금'
END
FROM Member m // 단순 CASE 식
SELECT CASE t.name
WHEN 'TEAM A' THEN '인센티브110%'
WHEN 'TEAM B' THEN '인센티브120%'
ELSE '인센티브105%'
END
FROM Team t
// COALESCE: 하나씩 조회해서 null이 아니면 반환
SELECT COALESCE(m.username, '이름 없는 회원')
FROM Member m;
// NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
SELECT NULLIF(m.username, '관리자')
FROM Member m
사용자 정의 함수 호출
- 하이버네이트는 사용전 방언에 추가해야한다.
SELECT FUNTION('group_concat', i.name)
FROM Item i
Named 쿼리 - 정식 쿼리
- 미리 정의해서 이름을 부여해두고 사용하는 JPQL
- 어노테이션, XML에 정의
- 애플리케이션 로딩 시점에 초기화 후 재사용
- 애플리케이션 로딩 시점에 쿼리를 검증
- @Query == @NamedQuery
- XML이 항상 우선권을 가진다.
- 애플리케이션 운영 환경에 따라 다른 XML을 배포할 수 있다.
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQuery;
@Entity
@NamedQuery(
name = "Member.findByame",
query = "SELECT m FROM Member m WHERE m.name = :name"
)
public class Member {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
private String name;
public Member() {
}
public Member(final Long id, final Team team, final String name) {
this.id = id;
this.team = team;
this.name = name;
}
public Long getId() {
return id;
}
public Team getTeam() {
return team;
}
public void setTeam(final Team team) {
this.team = team;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
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("NamedQuery 테스트")
@Test
void namedQueryTest() {
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 Member findMember = em.createNamedQuery("Member.findByName", Member.class)
.setParameter("name", "MEMBER A")
.getSingleResult();
assertThat(findMember.getName()).isEqualTo("MEMBER A");
transaction.commit();
} catch (final Exception e) {
System.out.println(e.getMessage());
transaction.rollback();
} finally {
em.close();
}
emf.close();
}
}
출처
출처
728x90
728x90
'Lecture > [Tacademy] JPA 프로그래밍 기본기 다지기' 카테고리의 다른 글
8. Spring Data JPA와 QueryDSL 이해 (0) | 2020.12.17 |
---|---|
6. JPA 내부구조 (0) | 2020.12.16 |
5. 양방향 매핑 (0) | 2020.12.15 |
4. 연관관계 매핑 (0) | 2020.12.15 |
3. 필드와 컬럼 매핑 (0) | 2020.12.13 |