@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded Period workPeriod; // 근무 기간
@Embedded Address homeAddress; // 집 주소
// ...
}
@Embeddable
public class Period {
@Temporal(TemporalType.DATE) java.util.Date startDate;
@Temporal(TemporalType.DATE) java.util.Date endDate;
//..
public boolean isWork(Date date) {
//..
}
}
새로 정의한 값 타입들은 재사용 가능하고 응집도 높음
해당 값 타입만 사용하는 의미 있는 메서드도 만들 수 있음
@Embeddable: 값 타입을 정의하는 곳에 표시
@Embedded: 값 타입을 사용하는 곳에 표시
기본 생성자가 필수
엔티티의 값일 뿐이므로 값이 속한 엔티티의 테이블에 매핑
임베디드 타입은 값 타입을 포함하거나 엔티티 참조 가능
@AttributeOverride: 속성을 재정의하는 어노테이션
임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null이 됨
값 타입과 불변 객체
값 타입을 여러 엔티티에서 공유하면 위험하다.
같은 값 타입을 여러 엔티티에서 참조
따라서 값을 복사해서 사용해야 함
하지만 직접 정의한 값 타입은 자바의 기본 타입이 아닌 객체 타입
복사하지 않고 원본 참조 값을 직접 넘기는 것을 막을 방법이 없음
객체의 공유 참조는 피할 수 없음
따라서 부작용 없이 값 타입을 사용할 수 있도록 될 수 있으면 불변 객체로 설계해야 함
값 타입의 비교
동일성 비교
인스턴스의 참조 값을 비교, == 사용
동등성 비교
인스턴스의 값을 비교, equals() 사용
값 타입의 equals() 재정의 시에는 보통 모든 필드의 값을 비교하도록 구현
값 타입 컬렉션
값 타입을 하나 이상 사용하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 사용
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name = "FAVORITE_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<String>();
@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns
= @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
//...
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
//...
}
관계형 데이터베이스는 컬럼 안에 컬렉션을 포함할 수 없으므로 별도의 테이블을 추가해야 함
값 타입 컬렉션도 값 타입이므로 member가 영속화 될 때 함께 영속화됨
값 타입 컬렉션의 페치 전략은 LAZY가 기본(값 타입은 EAGER)
값 타입 컬렉션에 변경 사항이 발생하면, 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재값을 모두 다시 저장
why? 별도의 테이블에 저장되므로 원본 데이터를 찾기 어렵다는 문제
값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함
컬럼에 null 안되고 같은 값을 중복할 수 없음
따라서 값 타입 컬렉션 대신에 새로운 엔티티를 만들어 일대다 관계로 설정하는 것이 나음
추가로 영속성 전이 + 고아 객체 제거 적용하면 값 타입 컬렉션처럼 사용
섹션 10. ~ 11. 객체지향 쿼리 언어
JPA가 공식 지원하는 SQL
JPQL
Criteria 쿼리: JPQL을 편하게 작성하도록 도와주는 API, 빌더 클래스 모음
네이티브 SQL: SQL을 직접 사용
공식 지원은 아니지만 알아볼둘 만한 기능
QueryDSL
JDBC 직접 사용 또는 MyBatis 등 사용
JPQL
JPQL: 엔티티 객체를 조회하는 객체지향 쿼리. 특정 데이터베이스에 의존하지 않음
String jpql = "select m from Member as m where m.username = 'kim'";
List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();
→
select
member.id as id,
member.age as age,
member.team_id as team,
member.name as name
from
Member member
where
member.name='kim'
기본 문법
대소문자를 구분하지 않음(SELECT, FROM, AS 등)
엔티티의 속성은 대소문자 구분
별칭(alias)는 필수
테이블 이름이 아닌 엔티티 이름 사용
TypeQuery, Query
TypeQuery: 반환 타입이 명확할 때 지정
Query: 반환 타입이 명확하지 않을 때 지정(Object 타입 반환)
결과 조회
query.getResultList: 결과를 반환. 없으면 빈 컬렉션 반환.
query.getSingleResult: 결과가 정확히 하나일 때 사용
결과가 없으면 NoResultException
결과가 두 개 이상이면 NonUniqueResultException
파라미터 바인딩
JPQL은 위치 기준 파라미터 뿐 아니라 이름 기준 파라미터 바인딩도 지원(NamedJdbcTemplate 처럼)
프로젝션
프로젝션: SELECT 절에 조회할 대상을 지전하는 것
프로젝션 대상
엔티티
SELECT m FROM Member m
SELECT m.team FROM Member m
SELECT o.member, o.product FROM Order o
임베디드 타입
스칼라 타입
SELECT m.username FROM Member m
SELECT m.username, SELECT m.age FROM Member m
여러 값을 프로젝션 하는 경우 Object[]로 받거나 new로 DTO로 받을 수 있음
SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
이 때, new 키워드를 사용할 객체는 패키지 경로까지 모두 적어줘야 함
순서와 타입이 일치하는 생성자가 필요
결과를 TypeQuery로 넘길 수 있음
페이징 API
JPA는 페이징을 다음의 두 API로 추상화
setFirstResult(int startPosition): 조회 시갖 위치
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();
데이터베이스 방언에 상관 없이 페이징 가능
최적화를 위해서는 네이티브 쿼리를 사용해야 함
집합과 정렬
집합
COUTN, MAX, MIN, AVG, SUM 사용 가능
NULL 값은 무시
GROUP BY, HAVING
GROUP BY {단일값 경로 | 별칭}
HAVING 조건식
정렬(ORDER BY)
ASC, DESC
조인
내부 조인
SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName"
연관 필드를 사용(m.team)함에 주의
일반 SQL 조인처럼 사용할 수 없음
외부 조인
SELECT m FROM Member m LEFT JOIN m.team t
컬렉션 조인
일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인
다대일 조인 시 단일 값 연관 필드(m.team) 사용
일대다 조인 시 컬렉션 값 연관 필드(t.members) 사용
SELECT t, m FROM t LEFT JOIN t.members m
세타 조인
WHERE 절을 사용해 세타 조인 가능(내부 조인만 지원)
select count(m) from Member m, Team t where m.username = t.name
JOIN ON 절(JPA 2.1 이후)
조인 대상을 필터링
내부 조인의 ON 절은 WHERE 절과 같으므로 외부 조인에서 사용
select m, t from Member m left join m.team on t.name = 'A'
페치 조인
JPQL에서 성능 최적화를 위해 제공하는 기능으로, 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능
select m from Member m join fetch m.team
Member m에 연관된 Team t를 함께 조회
페치 조인 시에는 별칭 사용 불가능
컬렉션 페치 조인
select t from Team t join fetch t.members where t.name = '팀A'
일대다 조인 시 만약 중복된 컬럼이 있다면 조회 결과가 늘어남
일대일, 다대일 조인은 결과 증가 x
JPQL의 DISTINCT로 중복 제거
SQL에 DISTINCT를 추가하고 애플리케이션에서 한번 더 중복 제거
select distinct t from Team t join fetch t.members where t.name = '팀A'
페치 조인과 일반 조인의 차이
JPQL은 결과를 반환할 때 연관관계를 고려하지 않음. 단지 SELECT 절에 지정한 엔티티만 조회.
즉시 로딩으로 설정하거나 지연 로딩으로 설정하고 실제 연관관계 객체를 불러올 때 쿼리를 한번 더 실행
반면 페치 조인을 사용하면 연관된 엔티티도 함께 조회
성능 최적화 가능
글로벌 로딩 전략을 지연 로딩으로 설정해도 무시하고 페치 조인을 함
따라서 글로벌 로딩 전략은 될 수 있으면 지연 로딩을 사용하고 최적화가 필요한 경우에 페치 조인을 사용하는 것이 효과적
페치 조인 대상에는 별칭 설정 불가
둘 이상의 컬렉션 페치 불가능
컬렉션 x 컬렉션의 카테시안 곱이 만들어지므로
컬렉션 페치 조인에는 페이징 API 사용 불가능
BatchSize를 사용하거나 조회 방향을 뒤집는 형태로 사용(다대일이나 일대일 페치 조인에는 페이징 사용 가능하므로)
경로 표현식
JPQL에서 m.username과 같이 점을 찍어 객체 그래프를 탐색하는 것
상태 필드: 단순히 값을 저장하기 위한 필드
연관 필드: 연관관계를 위한 필드, 임베디드 타입 포함
단일 값 연관 피륻와 컬렉션 값 연관 필드로 나뉘어짐
상태 필드 경로: 경로 탐색의 끝
단일 값 연관 경로: 묵시적 내부 조인이 일어남. 계속 탐색 가능
컬렉션 값 연관 경로: 묵시적 내부 조인이 일어나며 계속 탐색 불가.
FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색 가능
묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어려우므로 명시적 조인을 사용하자
서브 쿼리
JPQL도 서브 쿼리를 지원하는데, WHERE, HAVING 절에서만 사용이 가능하며 SELECT, FROM 절에서는 사용 불가능함
섹션 9. 값 타입
기본값 타입
String
,int
, …임베디드 타입(복합 값 타입)
@Embeddable
: 값 타입을 정의하는 곳에 표시@Embedded
: 값 타입을 사용하는 곳에 표시@AttributeOverride
: 속성을 재정의하는 어노테이션값 타입과 불변 객체
값 타입의 비교
동일성 비교
동등성 비교
값 타입 컬렉션
@ElementCollection
,@CollectionTable
사용member
가 영속화 될 때 함께 영속화됨LAZY
가 기본(값 타입은EAGER
)섹션 10. ~ 11. 객체지향 쿼리 언어
JPA가 공식 지원하는 SQL
공식 지원은 아니지만 알아볼둘 만한 기능
JPQL
JPQL: 엔티티 객체를 조회하는 객체지향 쿼리. 특정 데이터베이스에 의존하지 않음
→
기본 문법
SELECT
,FROM
,AS
등)TypeQuery, Query
Object
타입 반환)결과 조회
query.getResultList
: 결과를 반환. 없으면 빈 컬렉션 반환.query.getSingleResult
: 결과가 정확히 하나일 때 사용NoResultException
NonUniqueResultException
파라미터 바인딩
JPQL은 위치 기준 파라미터 뿐 아니라 이름 기준 파라미터 바인딩도 지원(
NamedJdbcTemplate
처럼)프로젝션
프로젝션:
SELECT
절에 조회할 대상을 지전하는 것프로젝션 대상
SELECT m FROM Member m
SELECT m.team FROM Member m
SELECT o.member, o.product FROM Order o
SELECT m.username FROM Member m
SELECT m.username, SELECT m.age FROM Member m
여러 값을 프로젝션 하는 경우
Object[]
로 받거나new
로 DTO로 받을 수 있음SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
new
키워드를 사용할 객체는 패키지 경로까지 모두 적어줘야 함TypeQuery
로 넘길 수 있음페이징 API
JPA는 페이징을 다음의 두 API로 추상화
setFirstResult(int startPosition)
: 조회 시갖 위치setMaxResults(int maxResult)
: 조회할 데이터의 수집합과 정렬
조인
SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName"
m.team
)함에 주의SELECT m FROM Member m LEFT JOIN m.team t
m.team
) 사용t.members
) 사용SELECT t, m FROM t LEFT JOIN t.members m
select count(m) from Member m, Team t where m.username = t.name
select m, t from Member m left join m.team on t.name = 'A'
페치 조인
JPQL에서 성능 최적화를 위해 제공하는 기능으로, 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능
select m from Member m join fetch m.team
select t from Team t join fetch t.members where t.name = '팀A'
DISTINCT
로 중복 제거DISTINCT
를 추가하고 애플리케이션에서 한번 더 중복 제거select distinct t from Team t join fetch t.members where t.name = '팀A'
페치 조인과 일반 조인의 차이
JPQL은 결과를 반환할 때 연관관계를 고려하지 않음. 단지 SELECT 절에 지정한 엔티티만 조회.
반면 페치 조인을 사용하면 연관된 엔티티도 함께 조회
BatchSize
를 사용하거나 조회 방향을 뒤집는 형태로 사용(다대일이나 일대일 페치 조인에는 페이징 사용 가능하므로)경로 표현식
m.username
과 같이 점을 찍어 객체 그래프를 탐색하는 것서브 쿼리
JPQL도 서브 쿼리를 지원하는데, WHERE, HAVING 절에서만 사용이 가능하며 SELECT, FROM 절에서는 사용 불가능함