"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."
www.inflearn.com/course/ORM-JPA-Basic#
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그
www.inflearn.com
즉시 로딩에 있어서 주의 할 점?
* 실무에서는 즉시 로딩은 사실상 사용하면 안된다. 가급적 지연 로딩만 사용하자.
그렇다면 어째서 즉시 로딩은 사용하면 안되는 걸까?
* 즉시 로딩을 적용하면 전혀 예상하지 못한 SQL 이 발생하게 된다.
- 우선 데이터베이스 입장에서 테이블이 1,2 개만 그닥 느리지 않다.
하지만 실무에서는 보통 테이블의 갯수가 수십개가 넘어가는데, 그렇게 되면 완전히 다른 차원의 문제가 되어 버린다.
그럴 경우 find 메소드를 호출하게 되면 EAGER 로 되어있는 테이블들이 전부 다 조인하게 된다.
실무에서 테이블이 복잡하게 얽혀 있는 상황이라면 그냥 다 지연 로딩(LAZY) 을 통해 연관관계에 있는 도메인 클래스의 객체들을 프록시 객체로 생성해주자.
* 즉시 로딩은 JPQL 에서 N + 1 문제를 일으킨다.
find 메소드의 경우 기본 키를 찍어서 가져오는 것이기 때문에 JPA 가 내부적으로 최적화를 할 수 있다.
그런데 JPQL 은
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList(); 과 같은 코드에서 select m from Member m 과 같은 쿼리가 그대로 SQL 로 우선적으로 번역된다.
(일단 select * from Member 쿼리가 나간다.)
그러면 당연히 Member 에만 select 쿼리가 나간다.
그렇게 Member 데이터를 가져왔더니 Team 클래스의 객체가 즉시 로딩(가져올 때 무조건 값이 다 들어가 있어야 한다.)으로 선언되어 있으면, Member 쿼리가 나간 후 호출되는 Member 의 갯수만큼 EAGER(즉시 로딩) 으로 Team 클래스의 데이터를 가져오기 위해 별도로 쿼리가 나가게 된다.
- JpaMain.java
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
- hibernate SQL
Hibernate: // Member select 한 번 Team select 한 번, select 쿼리가 총 두 번 나오는 것을 볼 수 있다.
/* select
m
from
Member m */ select
member0_.MEMBER_ID as member_i1_3_,
member0_.createdBy as createdb2_3_,
member0_.createdDate as createdd3_3_,
member0_.lastModifiedBy as lastmodi4_3_,
member0_.lastModifiedDate as lastmodi5_3_,
member0_.team_TEAM_ID as team_tea7_3_,
member0_.USERNAME as username6_3_
from
Member member0_
Hibernate:
select
team0_.TEAM_ID as team_id1_7_0_,
team0_.createdBy as createdb2_7_0_,
team0_.createdDate as createdd3_7_0_,
team0_.lastModifiedBy as lastmodi4_7_0_,
team0_.lastModifiedDate as lastmodi5_7_0_,
team0_.name as name6_7_0_
from
Team team0_
where
team0_.TEAM_ID=?
이번엔 아래와 같이 코드를 작성한 후 애플리케이션을 실행해보자.
// Team 데이터 2개 생성
Team team = new Team();
team.setName("teamA");
em.persist(team);
Team teamB = new Team();
teamB.setName("teamB");
em.persist(teamB);
// 2개의 Member 데이터에 각자 다른 Team 데이터를 부여한다.
Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setTeam(teamB);
em.persist(member2);
em.flush();
em.clear();
// JPQL 문 실행
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
- hibernate SQL
Hibernate: // 일단 Member 데이터를 한번이 다 들고 왔다.
/* select
m
from
Member m */ select
member0_.MEMBER_ID as member_i1_3_,
member0_.createdBy as createdb2_3_,
member0_.createdDate as createdd3_3_,
member0_.lastModifiedBy as lastmodi4_3_,
member0_.lastModifiedDate as lastmodi5_3_,
member0_.team_TEAM_ID as team_tea7_3_,
member0_.USERNAME as username6_3_
from
Member member0_
Hibernate:
select
team0_.TEAM_ID as team_id1_7_0_,
team0_.createdBy as createdb2_7_0_,
team0_.createdDate as createdd3_7_0_,
team0_.lastModifiedBy as lastmodi4_7_0_,
team0_.lastModifiedDate as lastmodi5_7_0_,
team0_.name as name6_7_0_
from
Team team0_
where
team0_.TEAM_ID=?
Hibernate:
select
team0_.TEAM_ID as team_id1_7_0_,
team0_.createdBy as createdb2_7_0_,
team0_.createdDate as createdd3_7_0_,
team0_.lastModifiedBy as lastmodi4_7_0_,
team0_.lastModifiedDate as lastmodi5_7_0_,
team0_.name as name6_7_0_
from
Team team0_
where
team0_.TEAM_ID=?
위의 SQL 문을 보면 Member 테이블을 select 한 이후에 보니 Member 와 연관관계를 가지고 있는 Team 클래스의 데이터가 2개 존재하여 select 쿼리를 2번 날려서 해당 데이터를 가져온 것을 볼 수 있다.(다른 팀이기 때문에 영속성 컨텍스트에도 없어서 따로따로 가져와야 한다.)
더 많은 숫자의 데이터가 이런 방식으로 구성되어 있다면 쿼리가 속칭 N + 1(N 이 결과가 10개면 최초 쿼리 1개로 인해 하나가 더 추가되어 11개의 쿼리가 나오게 된다.) 문제를 일으키게 된다.(즉시 로딩의 문제점)
그런데 이를 LAZY 방식으로 바꾸면 JPQL 에서 쿼리를 보낸 Member 클래스에 대한 데이터를 제외한 다른 데이터, 즉 연관관계를 가지고 있는 Team 클래스에 대한 데이터에 대해 select 와 같은 쿼리가 나오지 않게 된다.
(아직 사용하고 있지 않기 때문 - 프록시 객체로 Team 클래스의 객체가 생성된다.)
위의 설명과 같이 모든 연관관계를 LAZY 방식으로 연관관계의 프록시 객체로 생성해준 다음, N + 1 과 같은 문제를 해결하면서 즉시 로딩을 수행 할 수 있는 방법이 있다.
* JPQL Fetch Join
: 동적으로 원하는 데이터를 선택하여 한번에 가져오는 것(LAZY 설정으로 인해 프록시 객체로 생성된 객체에 대해 즉시 로딩으로 한방에 테이블을 조인해서 데이터를 같이 가져올 수 있다.)
-> 다음에 JPQL 관련 강의에서 자세히 설명된다.
- JpaMain.java
// JPQL Fetch Join(LAZY 설정에서 즉시 로딩)
List<Member> members = em.createQuery("select m from Member m join fetch m.team", Member.class)
.getResultList();
- hibernate SQL
Hibernate:
/* select
m
from
Member m
join
fetch m.team */ select
member0_.MEMBER_ID as member_i1_4_0_,
team1_.TEAM_ID as team_id1_9_1_,
member0_.createdBy as createdb2_4_0_,
member0_.createdDate as createdd3_4_0_,
member0_.lastModifiedBy as lastmodi4_4_0_,
member0_.lastModifiedDate as lastmodi5_4_0_,
member0_.team_TEAM_ID as team_tea7_4_0_,
member0_.USERNAME as username6_4_0_,
team1_.createdBy as createdb2_9_1_,
team1_.createdDate as createdd3_9_1_,
team1_.lastModifiedBy as lastmodi4_9_1_,
team1_.lastModifiedDate as lastmodi5_9_1_,
team1_.name as name6_9_1_
from
Member member0_
inner join
Team team1_
on member0_.team_TEAM_ID=team1_.TEAM_ID
위의 SQL 문을 보면 Team 클래스의 객체가 LAZY 로 설정되어 있음에도 불구하고 JPQL Fetch Join 으로 인해 Team 테이블까지 한번에 테이블 조인하여 데이터를 가져오는 것을 볼 수 있다.
@ManyToOne, @OneToOne 은 기본이 즉시 로딩으로 설정되어 있다.
그렇기 때문에 해당 어노테이션 으로 선언한 클래스 객체는 지연 로딩으로 설정해주려면 직접 fetch 속성을 이용해 LAZY 설정을 걸어줘야 한다.
(@OneToMany, @ManyToMany 는 기본이 지연 로딩이다.)
지연 로딩 활용?
자주 함께 사용하는 클래스들 끼리는 즉시 로딩을 사용하는 편이 좋다고는 하나, 굉장히 이론적인 부분으로 실무에서는 그냥 다 지연 로딩으로 설정해 줘야 한다.
그러다가 필요한 경우에만 JPQL Fetch Join 이나 Entity Graph 기능을 활용하여 즉시 로딩 기능을 만들어준다.
다음 글에서는 영속성 전이(CASCADE) 와 고아 객체에 대해 알아보자.
'JPA' 카테고리의 다른 글
영속성 전이(CASCADE) 와 고아 객체 - 2 (0) | 2020.10.17 |
---|---|
영속성 전이(CASCADE) 와 고아 객체 - 1 (0) | 2020.10.17 |
프록시 객체와 지연 로딩, 즉시 로딩 - 1 (0) | 2020.10.16 |
프록시와 연관관계 매핑 - 3 (0) | 2020.10.16 |
프록시와 연관관계 매핑 - 2 (0) | 2020.10.15 |