[JPA] 11. 객체지향 쿼리 언어2 - 중급 문법

지니🧸·2023년 2월 20일
0

Spring Boot & JPA

목록 보기
18/35

본 문서는 인프런의 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한) 강의를 공부하며 작성한 개인 노트입니다.

🚑 경로 표현식

경로 표현식: .(점)을 찍어 객체 그래프를 탐색하는 것
(예) m.username (상태 필드), from Member m m.team t (단일 값 연관필드), m.orders o (컬렉션 값 연관 필드)

  • 상태 필드, state field: 값을 저장하기 위한 필드
    • 경로 탐색의 끝 > 탐색 X
  • 연관 필드, association field: 연관관계를 위한 필드
    • 단일값 연관 필드: 대상이 엔티티
      • @ManyToOne, @OneToOne
      • 묵시적 내부 조인 (inner join) 발생
      • 탐색 O
    • 컬렉션값 연관 필드: 대상이 컬렉션
      • @OneToMany, @ManyToMany
      • 묵시적 내부 조인 발생
      • 탐색 X
      • FROM 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능
        select m.username From Team t join t.members m

조인

  • 명시적 조인: join 키워드 사용
    • select m from Member m join m.team t
  • 묵시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인 발생
    • 내부조인만 가능
    • select m.team from Member m

실무

  • 묵시적 조인은 사용하지 말자
  • 조인은 SQL 튜닝에 중요

🛵 페치 조인

페치 조인, fetch join

  • SQL 조인이 아님
  • JPQL에서 성능 최적화를 위해 제공함
  • 연관된 엔티티/컬렉션을 SQL 한번에 함께 조회
  • join fetch 명령어
    [Left [OUTER] / INNER] JOIN FETCH (조인 경로)
  • (예) 회원 조회하면서 연관된 팀도 함께 조회
    • 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'

한계

(예) 회원 두명이 팀A 소속 > TEAM JOIN MEMBER 실행시 팀A는 레코드 두 row

  • 일대다 관계에서만 일어나는 현상 (다대일 X)

  • DISTINCT
    JPQL

select distinct t
from Team t join fetch t.members
where t.name='팀A'
  • SQL에 DISTINCT를 추가해도 데이터가 달라서 SQL 결과 중복제거 실패
    • ID, FK 등 모든 정보가 같아야지 중복제거 됨

페치 조인 vs. 일반 조인

일반 조인

  • 연관된 엔티티를 함께 조회 X
    페치 조인
  • 연관된 엔티티도 함께 조회 > 즉시 로딩
  • 객체 그래프를 SQL 한번에 조회

특징과 한계

  • 페치 조인 대상에는 별칭을 줄 수 없음
    • 하이버네이트에서는 가능하지만 가급적 사용 X
    • (예) select t From Team t join fetch t.members **as m**;
      • as m 하면 안됨
  • 둘 이상의 컬렉션은 페치 조인 불가
  • 컬렉션을 페치 조인하면 페이징 API 사용 불가
    • setFirstResult, setMaxResults
    • 단일값 (일대일/다대일) 연관필드는 페치조인 가능
    • 하이버네이트는 경고 후 메모리에서 페이징 > 추천 X
    • 솔루션: persistence.xml
      <property name = "hibernate.default_batch_fetch_size" value="100">
  • 연관된 엔티티를 SQL 한번으로 조회 > 성능 최적화
  • 엔티티에 직접 적용하는 글로벌 로딩보다 우선함
    • 글로벌 로딩: @OneToMany(fetch = FetchType.LAZY)
  • 페치 조인은 객체 그래프 유지할 때 사용
  • 여러 테이블을 조인해서 엔티티가 가진 모양이 전혀 다른 결과를 내야할 때: 일반 조인 사용 > 필요한 데이터들만 조회해서 DTO로 반환

🛺 다형성 쿼리

다형성 설계 > 부모 자식 관계 등

TYPE

조회 대상을 특정 자식으로 한정
(예) Item 중 Book, Movie 조회

select i from Item i
where type(i) IN (Book, Movie)

TREAT

자바의 타입 캐스팅과 유사

  • 상속 구조에서 부모 타입을 자식 타입으로 다룰 때
  • FROM, WHERE, SELECT 사용

🏎️ 엔티티 직접 사용

JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본키값 사용

  • JPQL
    • 엔티티의 아이디 사용 - select count(m.id) from Member m
    • 엔티티 직접 사용 - select count(m) from Member m
    • 각각 파라미터로 넘겨도 동일
  • SQL 결과
    • select count(m.id) as cnt from Member m

외래키 값 사용 예시

  • JPQL
    • select m from Member where m.team = :team";
    • select m from Member m where m.team.id = :teamId";
  • SQL - select m.* from Member m where m.team_id=?

🚃 Named 쿼리

@NamedQuery

  • 미리 정의해서 이름을 부여해두고 사용
    • 정적 쿼리만 가능
  • 쿼리 재활용
  • 어노테이션/XML에 정의
  • 애플리케이션 로딩 시점에 초기화 후 재사용
  • 애플리케이션 로딩 시점에 쿼리 검증
  1. Member class에 @NamedQuery 생성
@Entity
@NamedQuery(
		name = "Member.findByUsername",
        query = "select m from Member m where m.username = :username"
)
public class Member {}

애플리케이션 적용 예시

em.createNamedQuery("Member.findByUsername", Member.class)
		.setParameter("username", "회원1")
        .getResultList();
  1. XML에 정의해서 사용하기
    persistence.xml에 정의
  • xml이 항상 우선권을 가짐
  1. 스프링 데이터 JPA 혼합 방식
public interface UserRepository extends JpaRepository<User, Long> {
	@Query("select u from User u where u.emailAddress=?1")
    User findByEmailAddress(String emailAddress);
}

🦼 벌크 연산

벌크 연산: pk 한건만 업데이트하는 경우를 제외한 모든 경우

  • 쿼리 한번으로 여러 테이블 로우 변경 (엔티티)
  • UPDATE/DELETE 지원
  • executeUpdate() > 영향 받은 엔티티 수 반환
  • 하이버네이트는 INSERT (insert into .. select) 지원
    (예) 재고가 10개 미만인 모든 상품의 가격을 10% 상승
  • JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행: 재고가 10개 미만인 상품 리스트 조회 > 상품 엔티티의 가격을 10% 증가 > 트랜잭션 커밋 시점에 변경감지 동작
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();

주의

  • 벌크 연산은 영속성 컨텍스트를 무시하고 디비에 직접 쿼리
  • 솔루션1: 벌크 연산을 먼저 실행
  • 솔루션2: 벌크 연산 수행 (JPQL 실행이기 때문에 flush됨) 후 영속성 컨텍스트 초기화 (em.clear())
profile
우당탕탕

0개의 댓글