회원을 조회하면서 연관된 팀도 함께 조회 (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
inner join이기때문에 회원4는 조회되지 않는다.
String query = "select m from Member m";
List<Member> resultList = em.createQuery(query, Member.class).getResultList();
for (Member member : resultList) {
System.out.println("member = " + member.getUsername() + ", " + member.getTeam().getName());
// 멤버1, teamA(SQL) - 영속성 컨텍스트에 없기때문에
// 멤버2, teamA(1차캐시)
// 멤버3, teamB(SQL)
}
LAZY 타입으로 설정해두었기 때문에 Team은 프록시로 올려두고 getTeam().getName()를 사용할때 DB에서 가져고오고 1차캐시에 올려두게 되어있다.
그래서 멤버1의 Team 객체를 가져올때만 select 쿼리가 날라간거고, 1차캐시에 올라갔기때문에 멤버2의 Team 객체를 가져올때는 DB가 아니라 1차캐시에서 가져온거다.
멤버3의 Team객체를 얻으려면 1차캐시에 없기때문에 SQL문에 또 나간다
-> N+1문제 발생 ( 해결방법은 join fetch )
String query = "select m from Member m join fetch m.team";
- 페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩X
이미 Member랑 Team의 데이터를 Join을 통해 다 가져왔기때문에 프록시가 아니고, 1차캐시에 다 담아져있어서 team의 데이터를 사용할때 SQL문이 날라가지않는다.
일대다 관계, 컬렉션 페치 조인
- 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'
String query = "select t from Team t join fetch t.members";
List<Team> resultList = em.createQuery(query, Team.class).getResultList();
for (Team team : resultList) {
System.out.println(team.getName() + ", " + team.getMembers().size());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member);
}
}
team 입장에서는 같은 teamA 하나인데, member 입장에서는 2개이기때문에 row가 두줄이 나온다. 따라서 같은게 2개나온다. JPA는 row가 3인지 100인지는 모른다. 그냥 결과를 보여줄 뿐인다.
DISTINCT 추가시 결과
teamname = 팀A, team = Team@0x100
-> username = 회원1, member = Member@0x200
-> username = 회원2, member = Member@0x300
String query = "select t from Team t join t.members m ";
페치 조인 대상에는 별칭을 줄 수 없다.
!!! 둘 이상의 컬렉션은 페치 조인 할 수 없다 !!!
컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.
연관된 엔티티들을 SQL 한 번으로 조회 - 성능 최적화
엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함
실무에서 글로벌 로딩 전략은 모두 지연 로딩
최적화가 필요한 곳은 페치 조인 적용
JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용
**외래키로 사용될 때도 마찬가지이다.
Member 객체
< JPA >
@NamedQuery(name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
< Spring Data JPA에서 같은 역할>
@Querty("select ***...")
Member singleResult = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "멤버1")
.getSingleResult();
System.out.println("singleResult = " + singleResult);
// singleResult = Member{id=3, username='멤버1', age=29}
String sqlString = "update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(sqlString)
.setParameter("stockAmount", 10)
.executeUpdate();
벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접
쿼리
벌크 연산을 먼저 실행
벌크 연산 수행 후 영속성 컨텍스트 초기화
// 자동 flush() 호출
int resultCount = em.createQuery("update Member m set m.age = 20").executeUpdate();
System.out.println("resultCount = " + resultCount); // 3
System.out.println("member1.getAge() = " + member1.getAge()); // 29 출력
// DB에만 반영된거기때문에 ! DB는 20살, 출력은 29살이다
// 출력도 20살로 하기위해서는 em.clear()하고 그 다음 em.find()로 찾아와야한다.