경로 표현식
- 결론 : 묵시적 내부 조인(inner join)이 일어나므로 단일값 연관 필드 / 컬렉션 값 연관 필드 경로 표현식을 쓰지 말 것.
- 실무에서 묵시적 내부 조인은 절대 쓰지 말아야 한다.
- 경로 표현식은 .(점)을 찍어 객체 그래프를 탐색하는 것이다.
- 경로 표현식에는 상태 필드 / 단일 값 연관 필드 / 컬렉션 값 연관 필드 3가지가 있다.
select m.username // 상태 필드
from Member m
join m.team t // 단일 값 연관 필드
join m.orders o // 컬렉션 값 연관 필드
where t.name = '팀A'
- m.username / m.team / t.name / m.orders 모두 경로 표현식이다.
- 상태 필드 : 단순히 값을 저장하기 위한 필드다. 일반적인 자바 기본 타입의 컬럼들을 말한다.
- 예) m.useranme / t.name
- 더 이상 경로 탐색이 되지 않는다.
- 연관 필드 : 연관 관계를 위한 필드, 임베디드 타입
- 단일 값 연관 관계 필드 : 대상이 엔티티인 것을 말한다. (@ManyToOne / @OneToOne)
- 예) m.team
- 묵시적으로 내부 조인(inner join)이 일어난다.
- 계속 경로 탐색할 수 있다.
- ex) select m.team.name from Member m
- 임베디드 타입도 단일 값 연관 필드지만 연관관계가 없으므로 조인이 일어나지 않는다.
- 컬렉션 값 연관 필드 : 대상이 컬렉션인 것을 말한다. (@OneToMany / @ManyToMany)
- 예) m.order
- 묵시적으로 내부 조인(inner join)이 일어난다.
- 기본적으로 경로 탐색을 할 수 없으나, from절에서 별칭을 얻으면 별칭으로 탐색할 수 있다.
- 단일 값 연관 관계 필드 : 대상이 엔티티인 것을 말한다. (@ManyToOne / @OneToOne)
명시적 조인 / 묵시적 조인
- 명시적 조인 : join 키워드 직접 사용
- select m from Member m join m.team t
- 묵시적 조인 : 경로 표현식에 의해 묵시적으로 SQL 조인 쿼리 발생
- 내부 조인만 가능하다.
- select m.team from Member m
조인은 SQL 튜닝에 중요한 포인트이기 때문에 묵시적 조인을 아예 쓰지 말아야 한다. 항상 명시적 조인을 사용해야 한다.
페치 조인
- SQL에서 지원하는 조인 문법이 아니다.
- JPQL에서 지원하는 성능 최적화를 위해 제공하는 기능이다.
- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능이다.
- join fetch 명령어를 사용한다.
- [LEFT [OUTER] | INNER] JOIN FETCH 조인_경로
- 실무에서 엄청 많이 사용한다.
1. 엔티티 페치 조인
/* JPQL 쿼리 */
select m from Member inner join fetch m.team
/* SQL 쿼리 */
SELECT M.*, T.* from MEMBER M INNER JOIN TEAM T ON M.TEAM_ID = T.ID
- 페치 조인 사용시 연관된 모든 엔티티의 값(m.*, t.*)을 가져온다.
- 기존의 inner join에서 값을 Object[]로 받아야하는 것과 달리 Mebmber 내부에 team 변수의 값이 다 채워진 상태로 Member 타입으로 받을 수 있다.
- 그래서 성능 최적화를 위해 제공되는 기능이라고 한다.
- 즉, 객체 그래프를 그대로 유지하면서 값을 받을 수 있다.
🧷 참고) 객체 그래프 탐색
객체 그래프 탐색
![](https://blog.kakaocdn.net/dn/eepRyU/btryguox0P0/uKG9RsVJC1c5e9pSVvHNgK/img.jpg)
- 객체에서 회원(Member)이 소속된 팀(Team)을 조회할 때는 참조를 사용해서 연관된 팀을 찾으면 된다. 이것을 객체 그래프 탐색이라고 한다.
- ex) Team team = member.getTeam();
SQL에서는 방향이 없어서 양쪽 다 참조가 가능하지만 객체지향은 참조 방향이 단방향이다. 그러므로 SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래픔 탐색 범위가 정해진다. 비즈니스 로직에 따라 사용하는 객체 그래프가 언제 끊어질지 알 수 없는 객체를 함부로 탐색할 수 없다.
JPA를 사용하면 객체 그래프 탐색 문제 해결 가능
JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다. JPA는 연관된 객체를 사용하는 시점에 적절한 SQL을 실행한다. 따라서 JPA를 사용하면 연관된 객체를 신뢰하고 조회할 수 있다. (지연 로딩 기능)
지연 로딩 기능 : 실제 객체를 사용하는 시점에 데이터 베이스 조회를 한다. 그전까지는 조회를 미룬다.
🔍 페치 조인 사용 전
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select m from Member m";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member.getUsername() = " + member.getUsername() +
", " + member.getTeam().getName());
}
Hibernate:
/* select
m
from
Member m */ select
member0_.id as id1_1_,
member0_.age as age2_1_,
member0_.TEAM_ID as TEAM_ID5_1_,
member0_.type as type3_1_,
member0_.username as username4_1_
from
Member member0_
Hibernate:
select
team0_.id as id1_4_0_,
team0_.name as name2_4_0_
from
Team team0_
where
team0_.id=?
member.getUsername() = 회원1, 팀A
member.getUsername() = 회원2, 팀A
Hibernate:
select
team0_.id as id1_4_0_,
team0_.name as name2_4_0_
from
Team team0_
where
team0_.id=?
member.getUsername() = 회원3, 팀B
- n+1 문제 발생
- 처음 Member 엔티티를 조회할 때 Team 정보는 프록시 객체로 되어 있다.
- 그러므로 프록시 객체를 조회할 때 여러 줄의 쿼리문이 나가게 되어 n+1 쿼리 개수 문제 발생
🔍페치 조인 사용 후
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select m from Member m join fetch m.team"; // 페치 조인 사용
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member.getUsername() = " + member.getUsername() +
", " + member.getTeam().getName());
}
Hibernate:
/* select
m
from
Member m
join
fetch m.team */ select
member0_.id as id1_1_0_,
team1_.id as id1_4_1_,
member0_.age as age2_1_0_,
member0_.TEAM_ID as TEAM_ID5_1_0_,
member0_.type as type3_1_0_,
member0_.username as username4_1_0_,
team1_.name as name2_4_1_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
member.getUsername() = 회원1, 팀A
member.getUsername() = 회원2, 팀A
member.getUsername() = 회원3, 팀B
- fetch join 쿼리를 사용하여 n+1 문제 해결
- fetch join을 사용하여 처음 Member 엔티티를 조회할 때 Team 엔티티를 프록시 객체가 아닌 실제 엔티티로 조회 했다.
- 그래서 Member 엔티티에서 Team 엔티티를 접근해도 쿼리가 여러개가 생기지 않는다.
- fetch join을 통해 지연로딩 기능을 즉시 로딩 기능으로 바꾼 것이다.
2. 컬렉션 페치 조인
일대다 관계에서 페치조인을 사용할 수 있다.
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select t from Team t join fetch t.members"; // 페치 조인 사용
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() +
" | members=" + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
Hibernate:
/* select
t
from
Team t
join
fetch t.members */ select
team0_.id as id1_4_0_,
members1_.id as id1_1_1_,
team0_.name as name2_4_0_,
members1_.age as age2_1_1_,
members1_.TEAM_ID as TEAM_ID5_1_1_,
members1_.type as type3_1_1_,
members1_.username as username4_1_1_,
members1_.TEAM_ID as TEAM_ID5_1_0__,
members1_.id as id1_1_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
team = 팀A | members=2
-> member = jpql.Member@46c00568
-> member = jpql.Member@f9b5552
team = 팀A | members=2
-> member = jpql.Member@46c00568
-> member = jpql.Member@f9b5552
team = 팀B | members=1
-> member = jpql.Member@6d2d99fc
출력결과를 보면 team = 팀A | members=2 부분이 중복으로 2번 출력됐다. Member 엔티티를 모두 조회할 때 member1과 member2의 팀이 서로 팀A으로 같은팀이다. 그래서 같은 결과가 두 번 나왔다. 이처럼 일대다 관계에서 조회할 때는 데이터 개수가 뻥튀기 될 수 있다.
select from Team t join fetch t.members where t.name = '팀A'
- 이것을 수행하면 Team은 하나지만 Member가 1개 이상일 수 있다.
- 그래서 중복 결과가 나오면서 데이터 개수가 늘어날 수 있다.
- 팀A는 1개지만 그에 해당하는 멤버는 회원1과 회원2로 두개이기 때문에조회 결과는 위 표 처럼 2개의 row가 된다.
- 즉, 팀의 개수가 멤버의 개수와 동일할 수 있다.
- 이러한 중복 출력은 distinct 명령어로 제거할 수 있다.
3. 페치 조인과 DISTINCT
- JPQL의 DISTINCT 명령어는 SQL의 DISTINCT 기능에다가 어플리케이션에서 한번 더 중복을 제거한다.
- 이 특징을 이용해서 컬렉션 페치 조인에서 리스트가 중복되서 나오는 문제를 해결할 수 있다.
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
// distinct 사용하여 중복 결과 제거
String query = "select distinct t from Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() +
" | members=" + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
Hibernate:
/* select
distinct t
from
Team t
join
fetch t.members */ select
distinct team0_.id as id1_4_0_,
members1_.id as id1_1_1_,
team0_.name as name2_4_0_,
members1_.age as age2_1_1_,
members1_.TEAM_ID as TEAM_ID5_1_1_,
members1_.type as type3_1_1_,
members1_.username as username4_1_1_,
members1_.TEAM_ID as TEAM_ID5_1_0__,
members1_.id as id1_1_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
team = 팀A | members=2
-> member = jpql.Member@56ccd751
-> member = jpql.Member@6872f9c8
team = 팀B | members=1
-> member = jpql.Member@bdecc21
- distinct 명령어를 통해 중복된 결과를 제거했다.
4. 페치 조인과 일반 조인의 차이
🔍 일반 조인
- 연관 관계를 고려하지 않고 select 절에 지정한 엔티티만 조회한다.
- 연관된 엔티티에 대해서는 프록시나 컬렉션으로 반환한다.
- 지연 로딩 기능 사용.
- n+1 문제 발생
🔍 페치 조인
- 연관 관계의 모든 엔티티를 한꺼번에 조회한다.
- 즉시 로딩 기능 사용.
- n+1 문제 해결
- 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념이다.
일반 조인 사용
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select t from Team t join t.members m"; // 페치 조인 사용
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() +
" | members=" + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
Hibernate:
/* select
t
from
Team t
join
t.members m */ select
team0_.id as id1_4_,
team0_.name as name2_4_
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
Hibernate:
select
members0_.TEAM_ID as TEAM_ID5_1_0_,
members0_.id as id1_1_0_,
members0_.id as id1_1_1_,
members0_.age as age2_1_1_,
members0_.TEAM_ID as TEAM_ID5_1_1_,
members0_.type as type3_1_1_,
members0_.username as username4_1_1_
from
Member members0_
where
members0_.TEAM_ID=?
team = 팀A | members=2
-> member = jpql.Member@30cecdca
-> member = jpql.Member@37c5fc56
team = 팀A | members=2
-> member = jpql.Member@30cecdca
-> member = jpql.Member@37c5fc56
Hibernate:
select
members0_.TEAM_ID as TEAM_ID5_1_0_,
members0_.id as id1_1_0_,
members0_.id as id1_1_1_,
members0_.age as age2_1_1_,
members0_.TEAM_ID as TEAM_ID5_1_1_,
members0_.type as type3_1_1_,
members0_.username as username4_1_1_
from
Member members0_
where
members0_.TEAM_ID=?
team = 팀B | members=1
-> member = jpql.Member@7051777c
- 그냥 조인을 사용하면 select 절에서 Team 엔티티만 조회한다.
페치 조인 사용
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select t from Team t join fetch t.members"; // 페치 조인 사용
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() +
" | members=" + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
Hibernate:
/* select
t
from
Team t
join
fetch t.members */ select
team0_.id as id1_4_0_,
members1_.id as id1_1_1_,
team0_.name as name2_4_0_,
members1_.age as age2_1_1_,
members1_.TEAM_ID as TEAM_ID5_1_1_,
members1_.type as type3_1_1_,
members1_.username as username4_1_1_,
members1_.TEAM_ID as TEAM_ID5_1_0__,
members1_.id as id1_1_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
team = 팀A | members=2
-> member = jpql.Member@46c00568
-> member = jpql.Member@f9b5552
team = 팀A | members=2
-> member = jpql.Member@46c00568
-> member = jpql.Member@f9b5552
team = 팀B | members=1
-> member = jpql.Member@6d2d99fc
- select절을 보면 Team, Member 엔티티를 모두 조회했음을 알 수 있다.
5. 페치 조인의 한계
- 페치 조인 대상에는 별칭을 줄 수 없다.
- 둘 이상의 컬렉션은 페치 조인 할 수 없다.
- 컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.
🔍 패치 조인 대상에는 별칭을 줄 수 없다.
- 연관된 엔티티를 모두 조회하기 때문에 별칭을 사용할 수 없다.
- 하이버네이트에서는 사용 가능하나, 가급적 사용하지 말 것
- fetch join 대상은 on / where 등에서 필터링 조건으로 사용하면 안된다.
- 별칭을 준 후 on절에서 별칭으로 조건을 주면 OneToMany 관계에서 Collection 형태로 조회 되는 데이터가 전부 조회되지 않고 일부만 나오기 때문에 문제가 생길 수 있다.
🔍 둘 이상의 컬렉션은 페치 조인을 할 수 없다.
- 컬렉션 * 컬렉션의 카테시안 곱이 만들어지므로 주의해야 한다.
- 하이버네이트를 사용할 경우 MultipleBagfetchException이 발생한다.
🔍 컬렉션을 패치 조인하면 페이징 API를 사용할 수 없다.
- 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인을해도 페이징 기능을 쓸 수 있다.
- 일대다에서는 데이터 개수가 뻥튀기가 되므로 페이징 기능을 사용할 수 없다.
- 일대다 같은 경우 SQL을 다대일로 접근해서 페이징 API를 사용한다.
- 하이버네이트는 경고 로그를 남기고 메모리에서 페이징한다. (매우 위험한 방식)
- 데이터 개수가 100만건 일경우 100만건이 모두 메모리에 올라가고 페이징하는 방식이기 때문이다.
- 성능 이슈가 발생한다.
💡 해결 방안
✔ 일대다를 대다일로 방향을 전환해서 해결한다.
String query = "select t from Team t";
▼ 다대일로 반향 전환
String query = "select m from Member m join fetch m.team t";
✔ BatchSize()
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
- 지연로딩 상태이지만 조회할 때 members를 BatchSize의 size 만큼 조회한다.
BatchSize()는 글로벌 설정으로 할 수 있다.
/* persistence.xml */
<property name="hibernate.default_batch_fetch_size" value="100" />
글로벌 설정으로 하는것이 편하다.
6. 페치 조인 정리
- 모든 것을 페치 조인으로 해결할 수 없다.
- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.
- 여러 테이블을 조인해서 엔티티가 아닌 전혀 다른 결과를 내야하면, 페치 조인보다는 일반 조인을 사용하고 필요한 데이터를 조회해서 DTO로 반환하는 것이 효과적이다.
다형성 쿼리
- 상속관계(@Inheritance)로 구성된 엔티티를 JPA에서 조회하면 그 자식 엔티티도 같이 조회할 수 있다.
1. TYPE
- 상속 구조에서 조회 대상을 특정 자식으로 한정할 때 사용한다.
- 예) Item 중에서 Book, Movie 엔티티를 조회해라
/* JPQL */
select i from Item i where type(i) in (Book, Movie)
/* SQL */
select i from i where i.DTPE in ('B', 'M')
2. TREAT
- 상속 구조에서 부모 타입을 특정 타입으로 다룰 때 사용한다.
- JPA 표준은 from / where 절만 사용 가능하다.
- 하이버네이트에서는 select 절에도 가능하다.
/* JPQL */
select i from Item i where treat(i as Book).auther = 'kim'
/* SQL */
select i from Item i where i.DTYPE= 'B' and i.auther = 'kim'
Item을 자식 타입인 Book으로 다뤘다. (다운 캐스팅)
그래서 Book 필드인 author에 접근할 수 있다.
엔티티 직접 사용
- 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
1. 기본 키 값
String query = "select m from Member m where m = :member";
Member findMember = em.createQuery(query, Member.class)
.setParameter("member", member1)
.getSingleResult();
Hibernate:
/* select
m
from
Member m
where
m = :member */ select
member0_.id as id1_1_,
member0_.age as age2_1_,
member0_.TEAM_ID as TEAM_ID5_1_,
member0_.type as type3_1_,
member0_.username as username4_1_
from
Member member0_
where
member0_.id=?
- member1 엔티티가 sql에서 member1.id 기본 키 값으로 쓰인다.
- member가 영속성 컨텍스트에 없어도 된다. 식별자만 가지고 있으면 된다.
2. 외래 키 사용
String query = "select m from Member m where m.team = :team";
List<Member> members = em.createQuery(query, Member.class)
.setParameter("team", teamA)
.getResultList();
Hibernate:
/* select
m
from
Member m
where
m.team = :team */ select
member0_.id as id1_1_,
member0_.age as age2_1_,
member0_.TEAM_ID as TEAM_ID5_1_,
member0_.type as type3_1_,
member0_.username as username4_1_
from
Member member0_
where
member0_.TEAM_ID=?
- 기본키와 로직이 동일하다.
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();
실무에서 Named쿼리를 사용할 때 지금 형태가 아닌 SpringDateJPA에서 지원하는 Named쿼리를 사용한다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress =?1")
User findByEmailAddress(String emailAddress);
}
JPA의 Named 쿼리가 스프링 데이터 JPA에서 @Query 애노테이션으로 쓰인다.
🧷 참고
- em.createQuery("select ...") 처럼 JPQL을 직접 문자로 넘기는 것을 동적 쿼리라고 한다.
- Named 쿼리를 정적 쿼리라고 한다.
벌크 연산
- JPQL로 여러 건을 한 번에 수정하거나 삭제할 때 사용한다.
- 예) 재고 10개 미만인 모든 상품의 가격을 10% 상승할 때
- JPA 변경 감지 기능으로 실행하려면 SQL 쿼리 문을 너무 많이 사용해야한다.
1. 벌크 연산
🔍 update 벌크 연산
ex.1
String sql = "UPDATE Product p " +
"SET p.prce = p.price * 1.1 " +
"WHERE p.stockAmount < :stockAmount";
int resultCount = em.createQuery(sql)
.setParameter("stockAmount", 10)
.executeUpdate();
- executeUpdate() : 벌크 연산으로 영향을 받은 엔티티 개수를 반환한다.
ex.2
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
// 벌크 연산 실행전 em.flush() 자동 호출
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
em.clear();
- 벌크 연산하기 전에 미리 em.flush()가 자동으로 실행된다.
🔍 delete 벌크 연산
String sql = "DELETE FROM Product p " +
"WHERE p.price < :price";
int resultCount = em.createQuery(sql)
.setParameter("price", 100)
.executeUpdate();
2. 벌크 연산시 주의사항
- 벌크연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리한다는 특징이 있어서 주의해야 한다.
- 그래서 영속성 컨텍스트와 데이터베이스 간에 데이터 차이가 발생할 수 있다.
🔍 해결 방안
💡 em.refresh(entity) 사용
- 벌크 연산 직후에 em.refresh() 를 사용하여 데이터베이스에 다시 상품 조회를 하면 된다.
💡 벌크 연산 먼저 실행
- 벌크 연산을 가장 먼저 실행하면 이미 변경된 내용을 데이터베이스에서 가져온다.
- 가장 실용적인 해결책이다.
💡 벌크 연산 수행 후 영속성 컨텍스트 초기화
- 벌크 연산 후에 무조건 em.clear() 메서드를 호출하여 영속성 컨텍스트를 초기화 시킨다.
- 영속성 컨텍스트가 초기화되면 데이터베이스에서 다시 조회한다.
👀 참고 자료
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
https://joont92.github.io/jpa/JPQL/
[jpa] JPQL
JPA에서 현재까지 사용했던 검색은 아래와 같다. 식별자로 조회 EntityManager.find() 객체 그래프 탐색 e.g. a.getB().getC() 하지만 현실적으로 이 기능만으로 어플리케이션을 개발하기에는 무리이다. 그
joont92.github.io
1.3.3 객체 그래프 탐색
1. 설명 객체에서 회원(Member)이 소속된 팀을 조회할 때는 다음처럼 참조를 사용해서 연관된 팀을 찾으면 되는데, 이것을 객체 그래프 탐색이라 한다. Team team = member.getTeam(); member.getOrder().getOrd..
bros.tistory.com
'[JPA] > JPA 프로그래밍 - 기본편' 카테고리의 다른 글
[JPA] 객체지향 쿼리 언어1 - 기본 문법 (0) | 2022.03.31 |
---|---|
[JPA] 값 타입 (0) | 2022.03.31 |
[JPA] 프록시와 연관관계 관리 (0) | 2022.03.30 |
[JPA] 고급 매핑 (0) | 2022.03.29 |
[JPA] 다양한 연관관계 매핑 (0) | 2022.03.29 |