경로 표현식
- .을 찍어 객체 그래프를 탐색하는 것이다.
- 연관 필드 : 연관관계를 위한 필드
- 단일 값 연관 필드 : @XtoOne 대상이 엔티티이다.
- 컬렉션 값 연관 필드 : @XToMany 대상이 컬렉션이다.
- 상태 필드는 단순히 값을 저장하는 필드이며, 엔티티가 아니기 때문에 더 이상 탐색이 불가능하고 탐색의 종점이다.
class Member {
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
// jpql
m.username -> 상태 필드
m.team -> 단일 값 연관 필드
m.orders -> 컬렉션 값 연관 필드
- 단일 값 연관 경로는 A 엔티티 → B 엔티티로 탐색이 이동되고, 묵시적으로 내부 조인(Inner Join)이 발생한다. B 엔티티에 .을 찍어 계속해서 탐색이 가능하다.
- 컬렉션 값 연관경로는 묵시적인 내부 조인이 발생하지만 탐색을 더 이상 할 수없다. (size 조회는 가능하다.)
- 컬렉션 값을 탐색하는 경우에는 from절에 명시적으로 조인을 걸어 별칭을 얻어서 조회한다.
select m from Team t join t.members m
- 🔥 묵시적 조인을 지원하지만 반드시 명시적으로 조인절을 사용하자!!!
- JOIN 키워드를 직접적으로 사용하자.
select m from Member m join m.team t
select m.team from Member m
다형성 쿼리
- 상속 구조인 경우에
type, treat
문법을 사용할 수 있다. type
이 DiscriminatorColumn(DTYPE) 으로 바뀌어서 사용된다.
[JPQL] select i from Item i where type(i) IN (Book, Movie)
[SQL] select i.* from Item i where i.DTYPE in ('B','M')
treat
은 캐스팅과 유사하다.- 상속 구조에서 부모타입을 특정 자식 타입으로 다룰 때 사용한다.
// treat을 이용해 다운 캐스팅 효과를 낸다. auther는 Book의 필드
[JPQL] select i from Item i where tret(i as Book).auther = 'kim'
[SQL] select i.* from Item i where i.DTYPE = 'B' and i.auther = 'kim'
엔티티 직접 사용
- JPQL에서 엔티티를 직접 사용하는 경우 SQL에서는 해당 엔티티의 기본 키값으로 매핑되어 사용된다.
- JPQL에서 엔티티를 setParameter를 통해서 넘겨도 SQL에서는 기본 키 값으로 매핑된다.
[JPQL] select count(m) from Member m. // 엔티티를 넘긴 경우
[JPQL] select count(m.id) from Member m // 식별자를 넘긴 경우
[SQL] select count(m.id) from Member m
String query = "select m from Member m where m = :member";
List<Member> findMember = em.createQuery(query, Member.class)
.setParameter("member", member).getResultList();
- 연관관계의 엔티티를 직접 사용하는 경우 SQL에서는 해당 엔티티의 외래 키 값으로 매핑되어 사용된다.
String query = "select m from Member m where m.team = :team";
// teamA의 식별자로 매핑된다.
List<Member> findMember = em.createQuery(query, Member.class)
.setParameter("team", teamA).getResultList();
Named 쿼리
- 엔티티에 쿼리를 직접 정의하고 이름을 부여해서 사용하는 JPQL이다. (정적 쿼리만 가능하다.)
- @NamedQuery, XML에 정의해서 사용한다.
- 정적 쿼리(변하지 않음)이기 때문에 애플리케이션 로딩 시점에 초기화하고 쿼리를 검증하고 캐싱한다!!
- NamedQuery안에 문자열로 쿼리를 지정해서 실행은 되지만, 애플리케이션 실행 시에 에러를 발생시킨다!
@Entity
@NamedQuery(
// 관례 : 엔티티명.이름
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member { ... }
List<Member> findMember = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
Spring Data JPA에서 @Query 애노테이션을 이용한다.
벌크 연산
- SQL의 UPDATE, DELETE (PK를 이용한 한건 업데이트, 및 삭제 제외) 문을 벌크 연산이라 한다.
- JPA의 변경 감지를 이용해 모든 데이터를 변경하거나 삭제하려 하면 쿼리가 많이 발생된다!
- 데이터가 100만건이면 루프를 돌면서 100만 번 쿼리가 실행된다.
// 모든 회원 나이 변경하기
int resultCount = em.createQuery("update Member m set m.age = 20").executeUpdate();
- UPDATE, DELETE는 JPA의 표준 스펙에서 벌크연산을 지원한다.
- Hibernate는 (Insert into … select)로 삽입하는 INSERT 연산도 지원한다.
- 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 날린다.
- 영속성 컨텍스트에 값을 넣지 않고 벌크 연산을 먼저 실행한다.
- 벌크 연산을 수행한 후 영속성 컨텍스트를 초기화한다. (영속성 컨텍스트에 값이 있을 수도 있는 경우)
Member member = new Member();
member.setUsername("회원1");
member.setTeam(teamA);
member.setAge(0);
em.persist(member); // Flush 발생 (영속성 컨텍스트에는 나이가 0인 상태 회원)
// 벌크 연산 (DB에 직접 데이터 *반*영)
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
1) // 영속성 컨텍스트에 member가 있기 때문에 1차 캐시에서 조회한다 => age = 0 으로 출력
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.getAge() = " + findMember.getAge());
2) // 영속성 컨텍스트를 초기화하면 DB에서 영속성 컨텍스트로 가져오기 때문에 age = 20
em.clear();
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.getAge() = " + findMember.getAge());
벌크 연산은벌크 연산은 Spring Data JPA에서 @Modifying를 이용한다.
📄 References
김영한 님의김영한 님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
반응형
'Backend > JPA' 카테고리의 다른 글
Spring Data Jpa (0) | 2022.08.22 |
---|---|
페치 조인 (Fetch Join) (0) | 2022.08.03 |
JPQL 기본 (0) | 2022.07.29 |
값 타입 (0) | 2022.07.26 |
영속성 전이 (Cascade), 고아 객체 (0) | 2022.07.21 |