1. 경로 표현식
◍ 경로표현식: .(점)을 찍어 객체 그래프를 탐색하는 것
select m.username -> 상태 필드
from Member m
join m.team t. -> 단일 값 연관 필드
join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀A'
◍ 용어 정리
○ 상태필드(state field): 단순히 값을 저장하기 위한 필드(ex> m.username)
○ 연관필드(association field): 연관 관계를 위한 필드
◼ 단일값 연관 필드: @ManyToOne, @OneToOne, 대상이 엔티티(ex> m.team)
◼ 컬렉션 값 연관 필드: @OneToMany, @ManyToMany, 대상이 컬렉션(ex> m.orders)
◍ 특징
○ 상대 필드 경로 탐색: 경로 탐색의 끝, 탐색 안함.
◼ JPQL: select m.username, m.age from Member m
◼ SQL: select m.username, m.age from Member m
○ 단일 값 연관 경로 탐색: 묵시적 내부 조인 발생, 탐색함
◼ JPQL: select o.member from Order o
◼ SQL: select m.* from Order o inner join Member m on o.member_id = m.id
○ 컬렉션 값 연관 경로: 묵시적 내부 조인 발생, 탐색 안함 -> FROM절에서 명시적 조인 통해 별칭 얻으면 별칭 통해 탐색 가능.
○ 명시적, 묵시적 조인
◼ 명시적 조인: join 키워드 직접 사용
◻ select m from Member m join m.team t
◼ 묵시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인 발생(내부 조인만 가능)
◻ select m.team from Member m
◼ 경로 탐색을 사용한 묵시적 조인 시 주의 사항
◻ 항상 내부 조인
◻ 컬렉션은 경로 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야함
◻ 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM(JOIN) 절에 영향을 줌
○ 예제
◼ select o.member.team from Order o -> 성공
◼ select t.members frokm Team -> 성공
◼ select t.members.username from Team t -> 실패
◼ select m.username from Team t join t.members m -> 성공
◻
◍ 실무조언
○ 가급적 묵시적 조인 대신에 명시적 조인 사용
○ 조인은 SQL 튜닝에 중요 포인트
○ 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어려움.
2. 페치조인
◍ 페치 조인
○ SQL 조인 종류 아님
○ JPQL에서 성능 최적화를 위해 제공하는 기능
○ 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 기능
○ join fetch 명령어 사용
○ 페치 조인 :: = [LEFT[OUTER] | [INNER] JOIN FETCH 조인 경로
◍ 엔티티 페치 조인
- 회원을 조회 하면서 연관된 팀도 함께 조회. SQL보면 회원 뿐만 아니라 팀(T.*)도 함께 셀렉
○ 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
◍ 컬렉션 페치조인 (일대다 관계, 컬렉션 페치 조인)
○ JPQL: select t from Team t join fetch t.members where t.name = '팀A'
○ SQL: Select T.*, M.* from team t inner join member m on T.id = M.team_id where T.name = '팀A'
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'" List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
for(Team team : teams) {
System.out.println("teamname = " + team.getName() + ", team = " + team); for (Member member : team.getMembers()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println(“-> username = " + member.getUsername()+ ", member = " + member); }
}
◍ 페치 조인과 DISTINCT
○ SQL의 DISTINCT는 중복 된 결과를 제거하는 명령
○ JPQL의 DISTINCT는 2가지 기능 제공
◼ SQL에 DISTINCT 추가
-> select distint t from Team t join fetch t.members where t.name = '팀A'
-1. SQL에서 DISTINCT추가 하지만 데이터 달라 SQL결과에서 중복 제거 실패
-2. DISTINCT가 추가로 애플리케이션에서 중복 제거 시도 (같은 식별자를 가진 Team 엔티티 제거)
--> 하이버네이트65 부터는 DISTINCT 명령어를 사용하지 않아도 애플리케이션에서 중복제거가 자동으로 적용.
◼ 애플리케이션에서 엔티티 중복 제거
◍ 페치 조인과 일반 조인의 차이
○ 일반 조인 실행시 연관된 엔티티를 함께 조회 하지 않음
◼ JPQL: select t from Team t join t.members m where t.name = '팀A'
◼ SQL: SELECT T.* FROM TEAM T INNER JOIN MEMBER M ON T.ID = M.TEAM_ID WEHRE T.NAME = '팀A'
○ JPQL은 결과를 반환할 때 연관관계 고려하지 않음
○ 단지 SELECT 절에 지정한 엔티티만 조회함.
○ 여기서는 팀 에티티만 조회하고, 회원 엔티티는 조회안함.
○ 페치 조인은 연관된 엔티티를 함께 조인함
◼ JPQL: select from Team t join fetch t.members where t.name = '팀A'
◼ SQL: SELECT T.*, M.* FROM TEAM T INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME = '팀A'
○ 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
○ 페치 조인은 객체 그래프를 SQL한번에 조회하는 개념.
◍ 페치 조인의 특징과 한계
○ 연관된 엔티티들을 SQL한번으로 조회 - 성능 최적화
○ 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함
◼ @OneToMany(fetch=FetchType.LAZY //글로벌 로딩 전략
○ 실무에서 글로벌 로딩 전략은 모두 지연로딩
○ 최적화가 필요한 곳은 페치 조인 적용
◍ 정리
○ 모든 것을 페치 조인으로 해결할 수 없음
○ 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적
○ 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 다른 결과를 내야하면, 페치 보다 일반 조인을 사용 & 필요한 데이터들만
조회해 DTO로 반환
3. 다형성 쿼리
◍ TYPE
○ 조회 대상을 특정 자식으로 한정(예> Item 중에 Book, Movie 조회해라)
◼ JPQL: select i from Item i where type(i) IN (Book, Movie)
◼ SQL: select i from i where i.DTYPE in ('B', 'M')
◍ TREAT (예> 부모인 Item과 자식 Book이 있다.)
○ 자바 타입의 캐스팅과 유사
○ 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용
○ FROM, WHERE, SELECT (하이버네이트 지원) 사용
◼ JPQL: select i from Item i where treat(i as Book).author = 'kim'
◼ SQL: select i.* from Item i where i.DTYPE = 'B' and i.author = 'kim'
4. 엔티티 직접 사용
◍ 기본 키값
○ JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용
◼ JPQL
◻ select count(m.id) from Membe m // 엔티티의 아이디를 사용
◻ select count(m) from Member m //엔티티를 직접 사용
◼ SQL (JPQL 둘다 같은 SQL 실행)
◻ select count(m.id) as cnt from Member m
○ 엔티티를 파라미터로 전달
String jpql = “select m from Member m where m = :member”; List resultList = em.createQuery(jpql)
.setParameter("member", member) .getResultList();
○ 식별자를 직접 전달
String jpql = “select m from Member m where m.id = :memberId”; List resultList = em.createQuery(jpql)
.setParameter("memberId", memberId) .getResultList();
○ 실행된 SQL
select m.* from Member m where m.id=?
◍ 외래 키값
Team team = em.find(Team.class, 1L);
String qlString = “select m from Member m where m.team = :team”; List resultList = em.createQuery(qlString)
.setParameter("team", team) .getResultList();
String qlString = “select m from Member m where m.team.id = :teamId”; List resultList = em.createQuery(qlString)
.setParameter("teamId", teamId) .getResultList();
//실행된 SQL
select m.* from Member m where m.team_id=?
5. Named 쿼리
◍ 정적 쿼리
○ 미리 정의해서 이름을 부여해 두고 사용하는 JPQL
○ 정적 쿼리
○ 어노테이션, XML에 정의
○ 애플리케이션 로딩 시점에 초기화 후 재사용
○ 애플리케이션 로딩 시점에 쿼리를 검증
◍ 어노테이션
@Entity
@NamedQuery(
name = "Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
...
}
List<Member> resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
◍ XML에 정의
[META-INF/persistence.xml]
<persistence-unit name="jpabook" >
<mapping-file>META-INF/ormMember.xml</mapping-file>
[META-INF/ormMember.xml]
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
<named-query name="Member.findByUsername">
<query><![CDATA[
select m
from Member m
where m.username = :username
]]></query>
</named-query>
<named-query name="Member.count">
<query>select count(m) from Member m</query>
</named-query>
</entity-mappings>
◍ Named쿼리 환경에 따른 설정
○ XML이 항상 우선권을 가진다
○ 애플리케이션 운영 환경에 따라 다른 XML을 배포 할 수 있다.
6. 벌크 연산
◍ 기본
○ 예시: 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
○ JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행
◼ 1. 재고가 10개 미만인 상품을 리스트로 조회
◼ 2. 상품 엔티티의 가격을 10% 증가 한다
◼ 3. 트랜잭션 커밋 시점에 변경감지가 동작
-> 변경된 데이터가 100건이라면 100번의 UPDATE SQL 실행
○ 예제
◼ 쿼리 한번으로 여러 테이블 로우 변경(엔티티)
◼ executeUpdate()의 결과는 영향받은 엔티티 수 반환
◼ UPDATE, DELETE 지원
◼ INSERT (insert into .. select, 하이버네이트 지원)
String qlString = "update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(qlString)
.setParameter("stockAmount", 10)
.executeUpdate();
◍ 주의
○ 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리
◼ 벌크 연산을 먼저 실행
◼ 벌크 연산 수행 후 영속성 컨텍스트 초기화
'Spring > JPA' 카테고리의 다른 글
JPA Basic - JPQL Basic (0) | 2024.01.10 |
---|---|
JPA Basic - 값 타입 (0) | 2024.01.09 |
JPA Basic - 프록시, CASCADE, 고아객체 (0) | 2024.01.09 |
JPA Basic - 상속관계 매핑 (0) | 2024.01.09 |
JPA Basic - 엔티티 매핑 (0) | 2024.01.09 |