본문 바로가기

Spring/JPA & Hibernate

연관관계 관리 (즉시 로딩과 지연 로딩)

반응형

즉시 로딩 (EAGER)

JPA 구현체는 가능하면 조인을 사용하여 SQL 한 번에 함께 조회해온다. 

  • 그러나 비즈니스 로직 상, 필요없는 연관 데이터까지 조회할 경우 비효율적임
  • 또한, 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다.
  • 또한, JPQL에서 N+1 문제를 일으킨다.
  • 따라서 가급적 지연 로딩을 사용하도록 ( 실무에서는 즉시 로딩은 쓰지 말고 지연 로딩을 적용을 고려할 것 
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩
이기 때문에 LAZY로 추가 설정해야 함 
  • @OneToMany, @ManyToMany는 기본이 지연 로딩

public class JpaMain {

	public static void main( final String[] args ) {

		EntityManagerFactory emf = Persistence.createEntityManagerFactory( "hello" );

		EntityManager em = emf.createEntityManager();

		EntityTransaction tx = em.getTransaction();
		tx.begin();

		try {

			Team teamA = new Team();
			teamA.setName( "teamA" );
			em.persist( teamA );

			Member memberA = new Member();
			memberA.setUsername( "memberA" );
			memberA.setTeam( teamA );
			em.persist( memberA );

			em.flush();
			em.clear();

			Member m = em.find( Member.class, memberA.getId() );

			System.out.println( "m = " + m.getTeam().getClass() );
			System.out.println( "========================" );
			System.out.println( "team name = " + m.getTeam().getName() );

			tx.commit();
		} catch ( Exception e ) {
			// transaction rollback
			tx.rollback();
			e.printStackTrace();
		} finally {
			em.close();
		}
		emf.close();
	}
}

실행 결과에서도 볼 수 있듯이 member를 가져올 때 member와 연결되어 있는 모든 테이블 조인으로 가져온다.

 

실무에서는 즉시 로딩은 쓰지 말고 지연 로딩을 적용을 고려할 것 

왜? 한번에 가져오면 좋은 거 아닌가?

 

☝🏻 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다.

하나의 데이터만 조회하는 것이 아니기 때문, 보통 하나 이상의 로우를 조회할 텐데 이와 연관 관계가 있는 모든 테이블의 모든 연관 관계를 조사해서 이에 대한 조인 쿼리가 발생한다 생각해보자.

 

☝🏻 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다. 


@Entity
public class Member extends BaseEntity {

	@Id
	@GeneratedValue( strategy = GenerationType.IDENTITY )
	@Column( name = "MEMBER_ID" )
	private Long id;

	@Column( name = "USRNAME" )
	private String username;

	@ManyToOne( fetch = FetchType.EAGER ) // member 입장에서는 many, team 입장에서는 1
	@JoinColumn( name = "TEAM_ID" )
	private Team team;
}

Team teamA = new Team();
teamA.setName( "teamA" );
em.persist( teamA );

Team teamB = new Team();
teamA.setName( "teamB" );
em.persist( teamB );

Member memberA = new Member();
memberA.setUsername( "memberA" );
memberA.setTeam( teamA );
em.persist( memberA );

Member memberB = new Member();
memberB.setUsername( "memberB" );
memberB.setTeam( teamB );
em.persist( memberB );

em.flush();
em.clear();

List<Member> memberList = em.createQuery( "select m from Member m", Member.class ).getResultList();

FetchType을 EAGER로 설정하면, JPQL 시 SELECT 쿼리가 여러 번 나간다. (Member의 개수만큼..)

em.find()로 가져올 때에는 PK를 이용해서 가져오기 때문에 JPA가 내부적으로 최적화해주지만, JPQL의 경우 그대로 SQL로 변환하여 실행(MemberSELECT 함)한 후, 그 Member의 연관 관계 중 즉시 로딩으로 세팅되어 있는 Team을 가져오기 위해 이에 대한 쿼리가 발생하게 된다. 만약 Member가 10개라면 10번의 SELECT 쿼리가 실행될 것.

따라서 쿼리 1개에 연관 관계 엔티티를 가져오기 위해 데이터의 갯수 만큼 N 개의 추가 쿼리가 발생한다.

 

🔎  만약, 연관 관계의 데이터도 함께 한번에 가져와야할 경우

  • FATCH JOIN 을 쓰거나
  • @EntityGraph를 이용하거나
  • batch size로 N+1이 아닌 1 + 1으로 해결할 수 있다.

지연 로딩 (LAZY)


@Entity
public class Member extends BaseEntity {

	@Id
	@GeneratedValue( strategy = GenerationType.IDENTITY )
	@Column( name = "MEMBER_ID" )
	private Long id;

	@Column( name = "USRNAME" )
	private String username;

	@ManyToOne( fetch = FetchType.LAZY ) // member 입장에서는 many, team 입장에서는 1
	@JoinColumn( name = "TEAM_ID" )
	private Team team;
}

연관 관계를 LAZY로 설정하면, 엔티티 조회 시, 연관되어있는 엔티티(team)는 프록시로 객체가 생성되고, 실제 연관되어있는 엔티티(team)를 사용하는 시점에 그 프록시 객체를 초기화한다.(DB 쿼리를 실행해서 값 셋팅)

 

반응형

'Spring > JPA & Hibernate' 카테고리의 다른 글

JPQL(Java Persistence Query Language) - 1  (0) 2020.12.20
JPA 쿼리  (0) 2020.12.20
Transaction  (0) 2020.12.15
JPA 데이터 타입  (0) 2020.01.15
영속성 전이 CASCADE  (0) 2020.01.10