빙응의 공부 블로그
[Spring]자바 ORM 표준 JPA 프로그래밍 - 객체지향 쿼리 언어2 본문
📝경로 표현식
경로 표현식이란 .(점)을 찍어 객체 그래프를 탐색하는 것을 말한다.
경로 표현식은 여러개 존재한다.
SELECT m.username -> 상태 필드
FROM Member m
JOIN m.team t -> 단일 값 연관 필드
JOIN m.orders o -> 컬렉션 값 연관 필드
where t.name = "팀A";
상태 필드
단순히 값을 저장하기 위한 필드이다. EX) m.username
보통 경로 탐색의 끝이며, 탐색이 마무리되어 탐색을 하지 않는다.
연관 필드
연관관계를 위한 필드이다.
- 단일 값 연관 필드
- @ManyToOne, @OneToOne, 대상이 엔티티 EX) m.team
- 묵시적 내부 조인(inner join)이 발생하며, 탐색을 진행한다.
- 쿼리 튜닝에 조심히 생각해서 사용해야 한다. (조인에 대한 성능 문제 때문이다.)
묵시적 내부 조인
JPQL: select o.member from Order o
SQL: select m.*
from Orders o
inner join Member m on o.member_id = m.id
- 컬렉션 값 연관 필드
- @OneToMany, @ManyToMany, 대상이 컬렉션 EX): m.orders
- 묵시적 내부 조인이 발생하고, 탐색이 진행되지 않는다.
- FROM 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색이 가능
주의!
실무에서는 가급적 묵시적 조인 대신에 명시적 조인을 사용하자!!
묵시적 조인은 조인이 일어나는 상황을 파악하기 어렵다.
📝페치 조인(Fetch join)
페치 조인은 다음과 같은 특징을 통해 정의할 수 있다.
- SQL 조인 종류가 아니다
- JPQL에서 성능 최적화를 위해 제공한다.
- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능이다.
- JOIN FETCH 명령어 사용
사용 예제
가정 1. 회원을 조회하면서 연관된 팀도 함께 조회하고 싶다( 대신 SQL 한번에)
• [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
페치조인은 엔티티의 관계를 이용해 한번에 조회할 수 있게 해준다.
이로서 N+1 문제를 해결하고 성능을 향상시킨다.
컬렉션 페치 조인
일대다 관계, 컬렉션 페치 조인이다.
• [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'
컬렉션 조인은 주의점이 있다. 일대다 관계에서 데이터가 뻥튀기 되는 현상이 일어난다.
이것을 막기 위해 중복된 결과를 제거해주어야 한다.
🚩JPQL의 DISTINCT
JPQL의 DISTINCT는 2가지 기능이 있다.
- SQL에 DISTINCT를 추가
- 애플리케이션에서 엔티티 중복 제거
- 즉, 같은 식별자를 가진 엔티티를 제거한다.
(하이버네이트 6, 즉 최신버전부터는 중복 제거가 자동으로 적용된다.!!)
select distinct t
from Team t join fetch t.members
where t.name = ‘팀A
페치 조인과 일반 조인의 차이점
일반조인
- JPQL의 결과를 반환할 때 연관관계를 고려하지 않는다.
- 단지 SELECT 절에 지정한 엔티티만 조회할 뿐이다.
- 여기서는 팀 엔티티만 조회하고 회원 엔티티는 조회하지 않는다.
select t
from Team t join t.members m
where t.name = ‘팀A
페치조인
- JPQL의 결과를 반환할 때 연관관계를 고려해 함께 조회한다.(사실상 즉시 로딩)
- 객체 그래프를 SQL 한번에 조회하는 개념이다.
🚩페치 조인의 한계
- 페치 조인 대상은 별칭을 줄 수 없다.
- 페치 조인은 엔티티 경로를 통해 명시되기 때문에 별칭을 주지 않는다.
- 둘 이상의 컬렉션은 페치 조인 할 수 없다.
- 데이터 뻥튀기 문제
- 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
- 일대일, 다대일 같은 단일 값 연관 필드들은 페이징이 가능하다.
🚩실무 관점의 페치조인
- 연관된 엔티티들을 SQL 한번을 조회하여 성능 최적화
- 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선시한다.
- @OneToMany(fetch = FetchType.LAZY) //글로벌 로딩 전략
- 실무에서 글로벌 로딩 전략은 모두 지연 로딩이나 최적화가 필요한 곳은 페치 조인을 적용한다.
📝다형성 쿼리
JPA에서 다형성 쿼리를 사용하려면 조회 대상을 특정 자식으로 한정시켜야 한다.
TYPE
select i from Item i
where type(i) IN (Book, Movie)
해당 방식으로 조회하면 ITEM의 BOOK과 MOVIE를 조회한다.
TREAT(JPA 2.1)
자바의 타입 캐스팅과 유사하다.
상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다.
[JPQL]
select i from Item i
where treat(i as Book).author = ‘kim’
사실상 부모 ITEM을 다운 케스팅해서 사용하는 것이다.
📝엔티티 직접 사용
SQL 요소를 엔티티에 직접 사용해보자!
기본 키 값
JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용한다.
• [JPQL]
select count(m.id) from Member m //엔티티의 아이디를 사용
select count(m) from Member m //엔티티를 직접 사용
• [SQL]
(JPQL 둘다 같은 다음 SQL 실행)
select count(m.id) as cnt from Member m
그렇기에 위 코드도 같은 결과의 SQL문이 실행된다.
📝Named 쿼리
엔티티에 애노테이션으로 쿼리를 미리 선언해 놓는 것이다.
정적 쿼리에만 가능하다.
정의
@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();
특징은 다음과 같다.
- 미리 정의해서 이름을 부여해두고 사용하는 JPQL이다.
- 정적 쿼리에만 사용
- 어느테이션, XML에 정의 가능
- 애플리케이션 로딩 시점에 초기화 후 재사용한다.
- 애플리케이션 로딩 시점에 쿼리를 검증한다.
- 로딩 시점에서 잘못되었으면 에러로 출력해 미리 검증이 가능하다. (가장 좋은 오류는 로딩 시점 오류!)
📝벌크 연산
벌크 연산이란 대량의 데이터를 한번에 수정, 삭제하는 작업이다.
executeUpdate() 메서드를 통해 벌크 연산을 수행한다
벌크 연산은 JPA 변경 감지 기능으로 실행하려면 너무 많은 연산을 사용하게 된다.
그렇기에 JPA에서의 벌크 연산은 다른 것과 차별된 수행방식이 있어 은 주의점이 있다.
- 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리한다.
해결방법
- 벌크 연산을 먼저 실행
- 벌크 연산 실행 후 영속성 컨텍스트 초기화
public void deleteMemberAndPosts(Long memberId) {
// 벌크 연산을 통해 특정 회원(Member)의 게시글(Board)을 삭제
String jpql = "DELETE FROM Board b WHERE b.member.id = :memberId";
Query query = entityManager.createQuery(jpql);
query.setParameter("memberId", memberId);
int deletedCount = query.executeUpdate();
// 벌크 연산 이후에는 영속성 컨텍스트를 초기화하거나 주의해야 함
entityManager.clear();
// 특정 회원(Member) 삭제
Member member = entityManager.find(Member.class, memberId);
if (member != null) {
entityManager.remove(member);
}
}
위의 예제처럼 벌크 연산은 영속성 컨텍스트를 무시한다. 그렇기에 영속성 컨텍스트를 고려하여 설계하는 것이 중요하다.
'Spring > 인프런_JPA' 카테고리의 다른 글
[Spring]JPA 지연 로딩 성능 최적화 (0) | 2024.07.03 |
---|---|
[Spring]실전! 스프링 부트와 JPA 활용1 - 변경 감지와 병합 (0) | 2024.03.18 |
[Spring]자바 ORM 표준 JPA 프로그래밍 - 객체지향 쿼리 언어 (0) | 2024.02.23 |
[Spring]자바 ORM 표준 JPA 프로그래밍 - 값 타입 (1) | 2024.02.22 |
[Spring]자바 ORM 표준 JPA 프로그래밍 - 프록시와 연관관계 관리 (0) | 2024.02.19 |