int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
식별자가 없고 값만 있으므로 변경시 추적 불가
기본값 타입, 임베디드 타입, 컬렉션 값 타입
임베디드 타입
새로운 값 타입을 직접 정의할 수 있음
주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고 함
기본 생성자가 필수
임베디드 타입은 엔티티의 값일 뿐 (임베디드 타입 사용 전/후를 비교해도 매핑하는 테이블은 같음)
@Embedabble
public class Address {
private String city;
private String street;
private String zipcode;
}
@Entity
public class Member {
...
@Embedded
private Address address;
}
주의점
값 타입은 공유해서 사용하면 안됨. 즉, 여러 엔티티에서 공유하면 위험함.
따라서 값 타입을 불변화 하는 것이 중요함.
불변화를 하는 방법으로 생성자로만 값을 설정하고 수정자를 만들지 않으면 됨.
JPQL
기본 문법
대표적인 기본 문법은 select m from Member as m where m.age > 18
엔티티와 속성은 대소문자 구분 O
JPQL 키워드는 대소문자 구분 X
엔티티 이름 사용, 테이블 이름이 아님(Member)
별칭을 필수(m) (as는 생략가능)
TypeQuery, Query
TypeQuery: 반환 타입이 명확할 때 사용
TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
Query: 반환 타입이 명확하기 않을 때 사용
Query query = em.createQuery("SELECT m.username, m.age from Member m");
결과 조회
query.setParameter("username", "루키")
username으로 파라미터 바인딩을 수행
query.getResultList()
결과가 하나 이상일 때, 리스트 반환
결과가 없으면 빈 리스트 반환
query.getSingleResult()
결과가 단 하나일 때, 단일 객체 반환
결과가 없거나, 둘 이상이면 exception
프로젝션
SELECT 절에 조회할 대상을 지정하는 것
프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
예시
// 엔티티 프로젝션
SELECT m FROM Member m
// 엔티티 프로젝션
SELECT m.team FROM Member m
// 임베디드 타입 프로젝션
SELECT m.address FROM Member m
// 스칼라 타입 프로젝션
SELECT m.username, m.age FROM Member m
임베디드 타입 프로젝션을 반환하는 방법
Address address = em.createQuery("SELECT m.address FROM Member m", Address.class).getSingleResult();
스칼라 타입 프로젝션을 반환하는 방법
List<MemberDto> result = em.createQuery("SELECT new jpa.jpql.MemberDto(m.username, m.age) FROM Member m").getResultList();
단순 값을 DTO로 바로 조회
페키지 명을 포함한 전체 클래스명을 입력해야 함
순서와 타입이 일치하는 생성자 필요
페이징 API
JPA는 페이징을 다음 두 API로 추상화
setFirstResult(int startPosition): 조회 시작 위치 (0부터 시작)
setMaxResults(int maxResult): 조회할 데이터 수
String jpql = "SELECT m FROM Member m ORDER BY m.name";
List<Member> members = em.createQuery(jpql, Member.class)
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
조인
내부 조인
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
ON을 활용한 조인 대상 필터링
SELECT m, t FROM Member m LEFT OUTER JOIN m.team t on t.name = 'A';
서브 쿼리
SELECT m FROM Member m WHERE m.age > (SELECT avg(m2.age) from Member m2)
JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
SELECT 절도 가능
FROM 절의 서브 쿼리는 현재 JPQL에서 불가능 (조인으로 풀 수 있으면 풀어서 해결)
경로 표현식
.(점)을 찍어 객체 그래프를 탐색하는 것을 말함
SELECT m.username -> 상태 필드
FROM Member m
JOIN m.team t -> 단일 값 연관 필드
JOIN m.orders o -> 컬렉션 값 연관 필드
WHERE t.name = "팀A";
상태 필드
단순히 값을 저장하기 위한 필드
경로 탐색의 끝, 탐색 X
연관 필드
연관관계를 위한 필드
단일 값 연관 필드
@ManyToOne, @OneToOne, 대상이 엔티티
묵시적 내부 조인 발생, 탐색 O
컬렉션 값 연괄 필드
@OneToMany, @ManyToMany, 대상이 컬렉션
묵시적 내부 조인 발생, 탐색 X
FROM 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능
페치 조인
SQL 조인 종류 x
JPQL에서 성능 최적화를 위해 제공하는 기능
연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
join fetch 명령어를 사용
SELECT m FROM Member m JOIN FETCH m.team
SELECT M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID = T.ID
컬렉션 페치 조인의 주의점
일대다 관계일 때, 컬렉션 페치 조인을 사용할 수는 있음.
SELECT t FROM Team t JOIN FETCH t.members
문제는 DB 특성상, 1:N 관계는 조회시, 중복되는 컬럼이 생길 수가 있음
해당 문제를 DISTINCT로 풀 수 있음
SQL에 DISTINCT를 추가
SQL 컬럼 데이터가 완전히 일치하지 않기 때문에 중복 제거 실패
어플리케이션에서 엔티티 중복 제거
JPA가 추가로 어플리케이션에서 같은 식별자를 가진 TEAM 엔티티를 제거
페치 조인과 일반 조인의 차이
페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
페치 조인은 객체 그래프르 SQL 한번에 조회하는 개념
페치 조인의 특징과 한계
페치 조인 대상에는 별칭을 줄 수 없다.
팀과 연관된 회원이 5명일 때, 3명만 조회를 해서 객체 그래프 탐색을 할 경우 예상치 못한 문제(데이터가 지워지거나 원하는 결과가 반환되지 않음)가 발생할 확률이 높다. (해당 부분은 실제 사용해 봐야 알 듯)
애초에 이런 상황이 생기면 Team -> List<Member>를 조회하는 것이 아닌, List<Member> 5개를 조회하는 것이 좋다.
둘 이상의 컬렉션은 페치 조인 할 수 없다.
1 : N : N 구조를 가지게 되기 때문에 대량의 쿼리가 발생할 가능성이 매우 높다.
컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
일대일, 다대일 같은 단일 값 연괄 필드들은 페치 조인해도 페이징 가능
일대다는 DB 관점에서 보면 당연히 중복된 데이터 들을 가져오기 때문에 페이징이 불가능
하이버네이트는 경로 로그를 남기고 메모리에서 페이징을 해주지만 매우 위험한 기능이기 때문에 사용을 하면 안된다.
데이터가 100만건이라면 메모리에 100만건을 들고오기 때문
해결방법
조회 방향을 뒤집는 방법
String query = "SELECT m FROM Member m join fetch m.team t";
fetch join을 제거하고 BatchSize를 적용하는 방법
String query = "SELECT m FROM Member m";`
List<Team> result = em.createQuery(query, Team.class)
.setFirstResult(0)
.setMaxResults(2)
.getResultList();
@Entity
public class Team {
private Long id;
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
Dto를 사용해서 쿼리를 작성하는 방법
여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면, 페치 조인 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적
벌크 연산
다음과 같은 예시에서 사용
재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
이와 같은 예시는 JPA의 변경 감지 기능을 활용하기에는 너무 많은 SQL을 실행해야 함
쿼리 한 번으로 여러 테이블 로우 변경(엔티티)
executeUpdate()의 결과는 영향받은 엔티티 수 반환
INSERT, UPDATE, DELETE 쿼리 지원
int resultCount = em.createQuery("UPDATE Member m set m.age = 20")
.executeUpdate();
벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리
영속성 컨텍스트를 무시하기 때문에 주의해서 사용
벌크 연산을 수행한다면 반드시 영속성 컨텍스트를 초기화 해주어야 함
다음과 같은 상황에서는 JPA의 기본전략 때문에 자동으로 FLUSH가 발생함
Member member1 = new Member("member1");
Member member2 = new Member("member2");
// 자동으로 FLUSH
int resultCount = em.createQuery("UPDATE Member m set m.age = 20")
.executeUpdate();
spring.jpa.properties.org.hibernate.flushMode=COMMIT 으로 변경하면 flush를 트랜잭션이 COMMIT 되는 시점에 되도록 변경할 수도 있음
참고로 Spring Data JPA에서 @Modifying(clearAutuomatically = true)을 사용해서 영속성 컨텍스트를 초기화하는 작업을 수행할 수도 있음
값 타입
JPA의 데이터 타입 분류
엔티티 타입
값 타입
임베디드 타입
값 타입은 공유해서 사용하면 안됨. 즉, 여러 엔티티에서 공유하면 위험함.
따라서 값 타입을 불변화 하는 것이 중요함. 불변화를 하는 방법으로 생성자로만 값을 설정하고 수정자를 만들지 않으면 됨.
JPQL
기본 문법
select m from Member as m where m.age > 18
TypeQuery, Query
TypeQuery: 반환 타입이 명확할 때 사용
Query: 반환 타입이 명확하기 않을 때 사용
결과 조회
query.setParameter("username", "루키")
query.getResultList()
query.getSingleResult()
프로젝션
SELECT 절에 조회할 대상을 지정하는 것
프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
예시
임베디드 타입 프로젝션을 반환하는 방법
스칼라 타입 프로젝션을 반환하는 방법
페이징 API
조인
내부 조인
외부 조인
세타 조인
경로 표현식
.(점)을 찍어 객체 그래프를 탐색하는 것을 말함
상태 필드
연관 필드
페치 조인
SQL 조인 종류 x
JPQL에서 성능 최적화를 위해 제공하는 기능
연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
join fetch 명령어를 사용
컬렉션 페치 조인의 주의점
일대다 관계일 때, 컬렉션 페치 조인을 사용할 수는 있음.
문제는 DB 특성상, 1:N 관계는 조회시, 중복되는 컬럼이 생길 수가 있음
해당 문제를
DISTINCT
로 풀 수 있음페치 조인과 일반 조인의 차이
페치 조인의 특징과 한계
페치 조인 대상에는 별칭을 줄 수 없다.
Team -> List<Member>
를 조회하는 것이 아닌,List<Member>
5개를 조회하는 것이 좋다.둘 이상의 컬렉션은 페치 조인 할 수 없다.
컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
해결방법
조회 방향을 뒤집는 방법
fetch join을 제거하고 BatchSize를 적용하는 방법
Dto를 사용해서 쿼리를 작성하는 방법
여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면, 페치 조인 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적
벌크 연산
다음과 같은 예시에서 사용
이와 같은 예시는 JPA의 변경 감지 기능을 활용하기에는 너무 많은 SQL을 실행해야 함
쿼리 한 번으로 여러 테이블 로우 변경(엔티티)
벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리
다음과 같은 상황에서는 JPA의 기본전략 때문에 자동으로 FLUSH가 발생함
spring.jpa.properties.org.hibernate.flushMode=COMMIT
으로 변경하면 flush를 트랜잭션이 COMMIT 되는 시점에 되도록 변경할 수도 있음참고로 Spring Data JPA에서
@Modifying(clearAutuomatically = true)
을 사용해서 영속성 컨텍스트를 초기화하는 작업을 수행할 수도 있음