김영환님의 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편 보면서 공부한 내용입니다.
📝 경로 표현식
select m.username // 상태필드
from Member m
join m.team t // 단일 값 연관 필드
join m.orders o // 컬렉션 값 연관 필드
where t.name = '팀A'
select m.username from Member m
// 탐색O
select m.team.name from Member m
// 묵시적 내부 조인
select o.member from Order o
=> 결과 쿼리
select m.* from Orders o
inner join Member m on o.member_id = m.id
select t.members from Team t
📝 명시적 조인, 묵시적 조인
select m from Member m.join m.team t
select m.team from Member m
💡 주의!!
실무에서는 가급적 묵시적 조인 대신 명시적 조인 사용
📝 페치 조인(fetch join)
😖 페치 조인 사용 전
String query = "select m from Member m";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member m : result) {
System.out.println("member : " + m.getUsername() + ", " + m.getTeam().getName());
// 회원1, 팀A(SQL)
// 회원2, 팀A(1차캐시)
// 회원3, 팀B(SQL)
// 총 쿼리 : 3번 나감
// 회원 100명인 경우 : 쿼리가 N + 1 의 숫자로 나가게됨
// => fetch join으로 해결가능!
}
🤗 페치 조인 사용 후
String query = "select m from Member m join fetch m.team";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member m : result) {
System.out.println("member : " + m.getUsername() + ", " + m.getTeam().getName());
}
// 쿼리 결과 => 한 방 쿼리로 바로 조회
select
m1_0.id,
m1_0.age,
t1_0.id,
t1_0.name,
m1_0.username
from
Member m1_0
join
Team t1_0
on t1_0.id=m1_0.team_id
📝 컬렉션 페치 조인
String query = "select t from Team t join fetch t.members";
📝 페치 조인과 DISTINCT
📝 페치 조인과 일반 조인의 차이
📝 페치 조인의 특징과 한계
💡 페치 조인은 연관된 대상을 모두 끌어오는 것이기 때문에 별칭을 잘못 사용하면 연관된 데이터 수가 달라져서 데이터 무결성이 깨질 수 있다. 연관된 데이터 수가 달라진 상태에서 2차 캐시에 저장되면 다른 곳에서 조회할 때도 데이터 수가 달라져서 오류가 발생할 수 있기 때문에 별칭을 쓰지 않는다.
select m from Member m join fetch m.team t
📝 결론
📝 기본 키 값
select m from Member m where m = :member // 쿼리 날림
// 해당 엔티티의 기본키 값으로 where절 조회
Member result = em.createQuery(query, Member.class)
.setParameter("member", member)
.getSingleResult();
// sql 결과
select
m1_0.id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
Member m1_0
where
m1_0.id=?
📝 외래 키 값
String query = "select m from Member m where m.team = :team"; // m.team = team_id
List<Member> result = em.createQuery(query, Member.class)
.setParameter("team", teamA)
.getResultList();
// sql 결과
select
m1_0.id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
Member m1_0
where
m1_0.team_id=?
📝 Named 쿼리
@Entity
@NamedQuery(
name="Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member {}
// Main.class
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
📝 벌크연산
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
System.out.println("count = " + resultCount); // 결과 3
📝 벌크연산 주의
💡 나이를 20으로 수정한 후 로컬에서 조회를 하면 수정한 값이 아닌 수정 전 값이 조회된다. 이는 DB에만 수정되어있을 뿐 영속성 컨텍스트에는 수정 전 나이가 저장되어있기 때문이다. 그러므로 영속성 컨텍스트를 초기화하여 수정 후 나이를 반환하도록 해야한다.
// FLUSH 자동 호출
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
em.clear(); // 영속성 컨텍스트 초기화