JPA 기본 11
경로 표현
경로 표현식
점(.)을 찍어 객체 그래프를 탐색하는 것을 말한다.
예시
1 2 3 4 5
select m.username // 상태 필드 from Member m join m.team t // 단일 값 연관 필드 join m.orders o // 컬렉션 값 연관 필드 where t.name = 'teamA'
상태 필드
- 단순히 값을 저장하기 위한 필드다.
- 경로 탐색의 끝이다. 더 이상 탐색되지 않는다.
연관 필드
- 단일 값 연관 필드
- 대상이 엔티티다.
@ManyToOne
,@OneToOne
- 묵시적 내부 조인이 발생한다. 더 탐색된다.
- 컬렉션 값 연관 필드
- 대상이 컬렉션이다.
@OneToMany
,@ManyToMany
- 묵시적 내부 조인이 발생한다. 더 이상 탐색되지 않는다.
- FROM 절에서 명시적 조인을 통해 별칭 획득 시, 별칭을 통한 탐색은 더 할 수 있다.
명시적, 묵시적 조인
명시적 조인
- join 키워드를 직접 사용한다.
묵시적 조인
- 경로 표현식에 의해 묵시적으로 SQL 조인이 발생한다. 내부 조인만 가능하다.
묵시적 조인 시 주의사항
- 묵시적 조인은 항상 내부 조인을 사용하기에 경로 탐색 시 FROM 절에 영향을 줄 수 있음을 주의하자.
- 컬렉션은 경로 탐색의 끝으로 명시적 조인을 통해 별칭을 얻어 경로 탐색을 지속할 수 있다.
조언
- 묵시적 조인을 사용하지 말자!!
- 명시적 조인을 사용하자.
페치 조인(fetch join)
- SQL 조인 종류가 아니다.
- JPQL에서 성능 최적화를 위해 제공하는 기능이다.
- 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 기능을 말한다.
join fetch
명령어를 통해 사용 가능하다.
엔티티 페치 조인
회원을 조회하면서 연관된 팀도 함께 조회하고자 한다.
1 2
// JPQL select m from Member m join fetch m.team
컬렉션 페치 조인
예시
1 2
// JPQL select t from Team t join fetch t.members where t.name='teamA'
페치 조인과 DISTINCT
SQL의 DISTINCT는 중복 결과를 제거하는 명령이다.
JPQL에서는 2가지 기능을 제공한다.
- SQL에 DISTINCT를 추가
- 애플리케이션에서 엔티티 중복을 제거
- 같은 식별자를 가진 중복 엔티티를 제거해준다.
예시
1 2
// JPQL select distinct t from Team t join fetch t.members where t.name='teamA'
페치 조인과 일반 조인의 차이
- 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않는다.
- JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다.
- 단지 select 절에 지정한 엔티티만을 조회한다.
- 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회한다.(즉시 로딩)
- 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념이다.
페치 조인의 특징과 한계
- 페치 조인 대상에는 별칭을 줄 수 없다.
- 하이버네이트는 가능하다. 허나 사용을 피하도록 하자.
- 둘 이상의 컬렉션은 페치 조인 할 수 없다.
- 컬렉션 페치 조인 시 페이징 API 사용이 불가하다.
- 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 처리가 가능하다.
- 하이버네이트는 경고 로그를 남기고 메모리에서 페이징하는 매우 위험한 로직을 가진다.
- 연관된 엔티티들을 SQL 한 번으로 조회하여 성능을 최적화한다.
- 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선한다.
- 글로벌 로딩 전략 :
@OneToMany(fetch=FetchType.LAZY)
- 글로벌 로딩 전략 :
- 실무에서 글로벌 로딩 전략은 모두 지연 로딩을 따른다.
- 최적화가 필요한 곳은 페치 조인이 적용된다.
페치 조인 정리
- 모든 것을 페치 조인으로 해결하는 것은 불가능하다.
- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.
- 여러 테이블을 조인해 엔티티가 가진 모양과 전혀 다른 결과를 내야 하는 경우, 페치 조인보다 일반 조인을 사용하고 필요한 데이터만 조회하여 DTO로 반환하는 것이 효과적이다.
다형성 쿼리
- TYPE
- TREAT
엔티티 직접 사용
기본 키 값
JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용한다.
엔티티를 파라미터로 전달하는 경우
1 2
String jpql = "select m from Member m where m = :member"; List result = em.createQuery(jpql).setParameter("member", member).getResultList();
식별자를 직접 전달하는 경우
1 2
String jpql = "select m from Member m where m.id = :memberId"; List result = em.createQuery(jpql).setParameter("memberId", memberId).getResultList();
실행된 SQL 문?
1
select m.* from Member m where m.id=?
외래 키 값
엔티티를 파라미터로 전달하는 경우
1 2 3 4
Team team = em.find(Team.class, 1L); String qlString = "select m from Member m where m.team = :team"; List result = em.createQuery(qlString).setParameter("team", team).getResultList();
식별자를 직접 전달하는 경우
1 2 3 4
Team team = em.find(Team.class, 1L); String qlString = "select m from Member m where m.team.id = :teamId"; List result = em.createQuery(qlString).setParameter("teamId", teamId).getResultList();
실행된 SQL 문?
1
select m.* from Member m where m.team_id=?
Named 쿼리
정적 쿼리
- 미리 정의해서 이름을 부여해두고 사용하는 JPQL이다.
- 정적 쿼리
- 어노테이션, XML에 정의한다.
- 애플리케이션 로딩 시점에 초기화된다. 이후 재사용한다.
- 애플리케이션 로딩 시점에 쿼리를 검증한다.
쿼리 환경에 따른 설정
- XML이 항상 우선권을 가짐.
- 애플리케이션 운영 환경에 따라 다른 XML 배포 가능.
벌크 연산
- 쿼리 한 번으로 여러 엔티티의 로우를 변경하는 연산이다.
- executeUpdate()의 결과는 영향받은 엔티티 수를 반환한다.
- UPDATE, DELETE를 지원하고, 하이버네이트에서 INSERT를 지원한다.
주의할 점
- 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리한다. 꼬이는 것을 방지해야 한다.
- 벌크 연산 먼저 실행하고, 수행이 끝나면 영속성 컨텍스트를 초기화하는 방식으로 해결 가능하다.
Comments powered by Disqus.