자바 ORM 표준 JPA 프로그래밍 : 교보문고
자바 ORM 표준 JPA 프로그래밍 - 기본편 : 인프런
@MappedSuperclass
public class BaseEntity
{
@Id @GeneratedValue
private Long id;
private LocalDateTime createDate;
private LocalDateTime modifyDate;
private String createUser;
private String modifyUser;
private String name;
// Getter setter 생략
...
}
@Entity
public class Member extends BaseEntity
{
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
@Entity
public class Team extends BaseEntity
{
}
다음과 같은 객체 관계에서
Member
를 조회하면 항상Team
까지 조회가 될 것이다.
Application 에 로직에 따라서Team
의 정보가 필요 없는 경우에도 굳이 들고와야 한다.
이후 해당 어플리캐이션 사이즈가 커져서Member
객체 안에Team
을 포함한 많은 연관관계가 생기면 그때도Member
하나 조회하겠다고 연관관계 인 다른 객체들을 모두 들고 와야하는
비용이 발생한다. 이를 위해서 JPA 에서 Proxy 객체를 활용한지연로딩
을 제공한다.
자원을 필요로 하는 시점에 객체를 로드하는 기술
연관 관계의 Entity 가 실제로 필요한 시점에 로딩
@Entity
public class Member extends BaseEntity
{
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
연관관계 어노테이션의
fetch
속성에 사용한다.
선언할 경우 실제 Entity 가 아닌Proxy
객체가 생성된다.
해당 객체만 불러오는 기술 여기에서는Member
에 대한 것만 호출🍇 예시
Member m = em.find(Member.class, member.getId()); System.out.println("Member Class Type : "+m.getClass()); System.out.println("Team of Member's Class type : "+m.getTeam().getClass()); System.out.println("Proxy Initialize"); System.out.println("m.getTeam().getName() : "+m.getTeam().getName()); 결과 --- Member Class Type : class org.example.entity.Member Team of Member's Class type : class org.example.entity.Team$HibernateProxy$IHPqMI70 Proxy Initialize Hibernate: select team0_.id as id1_1_0_, team0_.createDate as createDa2_1_0_, team0_.createUser as createUs3_1_0_, team0_.modifyDate as modifyDa4_1_0_, team0_.modifyUser as modifyUs5_1_0_, team0_.name as name6_1_0_ from Team team0_ where team0_.id=? m.getTeam().getName() : A Team
fetch = FetchType.LAZY
로 선언된Team
객체 타입은Proxy
객체 타입이고,
Team
객체 필요 시m.getTeam().getName()
Team
조회Query
가 발생한다.
LazyLoading 을 통해 연관관계 객체 필요 시 에만 호출하여 사용할 수 있고 이에 따라 비용을 줄일 수 있다.
장점 : 비용 절감
이와 대조되게 연관관계인 객체 모두를 들고오는 기능도 제공한다.
@Entity
public class Member extends BaseEntity
{
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
연관관계에 있는 모든 객체들을 한번에 조회하여 영속화 한다
쉽게 말해서연관있는 모든객체를 한번에 다 가져온다
🍇 예시
Member m = em.find(Member.class, member.getId()); System.out.println("Member Class Type : "+m.getClass()); System.out.println("Team of Member's Class type : "+m.getTeam().getClass()); --- Hibernate: select member0_.id as id1_0_0_, member0_.createDate as createDa2_0_0_, member0_.createUser as createUs3_0_0_, member0_.modifyDate as modifyDa4_0_0_, member0_.modifyUser as modifyUs5_0_0_, member0_.name as name6_0_0_, member0_.TEAM_ID as TEAM_ID7_0_0_, team1_.id as id1_1_1_, team1_.createDate as createDa2_1_1_, team1_.createUser as createUs3_1_1_, team1_.modifyDate as modifyDa4_1_1_, team1_.modifyUser as modifyUs5_1_1_, team1_.name as name6_1_1_ from Member member0_ left outer join Team team1_ on member0_.TEAM_ID=team1_.id where member0_.id=? Member Class Type : class org.example.entity.Member Team of Member's Class type : class org.example.entity.Team
Team
객체 타입 또한 Entity 인것을 볼 수 있다.
즉시 Loading 은 객체와 연관관계인 모든 객체를 한꺼번에 조회하여 영속화한다.
연관관계가 많은 객체의 경우 조회 시 연관된 객체들 모두 조회하여필요 없는 Query 들이 발생한다.
@Entity
public class Team2 extends BaseEntity
{}
X 3
@Entity
public class Member extends BaseEntity
{
// @ManyToOne(fetch = FetchType.LAZY)
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM2_ID")
private Team2 team2;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM3_ID")
private Team3 team3;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM4_ID")
private Team4 team4;
Hibernate:
select
member0_.id as id1_0_0_,
member0_.createDate as createDa2_0_0_,
member0_.createUser as createUs3_0_0_,
member0_.modifyDate as modifyDa4_0_0_,
member0_.modifyUser as modifyUs5_0_0_,
member0_.name as name6_0_0_,
member0_.TEAM_ID as TEAM_ID7_0_0_,
member0_.TEAM2_ID as TEAM8_0_0_,
member0_.TEAM3_ID as TEAM9_0_0_,
member0_.TEAM4_ID as TEAM10_0_0_,
team1_.id as id1_1_1_,
team1_.createDate as createDa2_1_1_,
team1_.createUser as createUs3_1_1_,
team1_.modifyDate as modifyDa4_1_1_,
team1_.modifyUser as modifyUs5_1_1_,
team1_.name as name6_1_1_,
team2x2_.id as id1_2_2_,
team2x2_.createDate as createDa2_2_2_,
team2x2_.createUser as createUs3_2_2_,
team2x2_.modifyDate as modifyDa4_2_2_,
team2x2_.modifyUser as modifyUs5_2_2_,
team2x2_.name as name6_2_2_,
team3x3_.id as id1_3_3_,
team3x3_.createDate as createDa2_3_3_,
team3x3_.createUser as createUs3_3_3_,
team3x3_.modifyDate as modifyDa4_3_3_,
team3x3_.modifyUser as modifyUs5_3_3_,
team3x3_.name as name6_3_3_,
team4x4_.id as id1_4_4_,
team4x4_.createDate as createDa2_4_4_,
team4x4_.createUser as createUs3_4_4_,
team4x4_.modifyDate as modifyDa4_4_4_,
team4x4_.modifyUser as modifyUs5_4_4_,
team4x4_.name as name6_4_4_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.id
left outer join
Team2 team2x2_
on member0_.TEAM2_ID=team2x2_.id
left outer join
Team3 team3x3_
on member0_.TEAM3_ID=team3x3_.id
left outer join
Team4 team4x4_
on member0_.TEAM4_ID=team4x4_.id
where
member0_.id=?
Member 객체 하나 조회하겠다고 이런 대량의 코드들이 발생한다.
FetchType.EAGER
를 사용하고JPQL
을 사용할 때 발생한다 .🍇 예시
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
다음 코드가 실행될 경우
---- Hibernate: /* select m from Member m */ select member0_.id as id1_0_, member0_.createDate as createDa2_0_, member0_.createUser as createUs3_0_, member0_.modifyDate as modifyDa4_0_, member0_.modifyUser as modifyUs5_0_, member0_.name as name6_0_, member0_.TEAM_ID as TEAM_ID7_0_ from Member member0_ Hibernate: select team0_.id as id1_1_0_, team0_.createDate as createDa2_1_0_, team0_.createUser as createUs3_1_0_, team0_.modifyDate as modifyDa4_1_0_, team0_.modifyUser as modifyUs5_1_0_, team0_.name as name6_1_0_ from Team team0_ where team0_.id=?
조회 Query 가 두번 발생하는 것을 볼 수 있다.
위의JPQL
실행 시select m from Member m
가 먼저 실행되고 영속성 컨텍스트에 할당된다. 할당될때Team
객체와의 연관관계가FetchType.EAGER
인 것을 확인하여 다시Team
객체를 조회하는 Query 가 발생한여N+1
문제가 생긴다.
시간 순서대로 풀어보면
1.JPQL
실행 시select m from Member m
가 먼저 실행
2.Member
객체가 할당될 때Team
객체와의 연관관계가FetchType.EAGER
인 것을 확인
3.Team
객체를 조회
@ManyToOne, @OneToOne 은 기본이 즉시로딩이기 때문에 Lazy 로 설정하는 것이 좋다.
@ManyToOne
@Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ManyToOne { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default FetchType.EAGER; boolean optional() default true; }
@OneToOne
@Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface OneToOne { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default FetchType.EAGER; boolean optional() default true; String mappedBy() default ""; boolean orphanRemoval() default false; }