상세 컨텐츠

본문 제목

[JPA&Hibernate] 지연로딩을 가능하게 하는 Proxy란?

Backend

by choiDev 2023. 12. 28. 00:46

본문

JPA Proxy란? 

JPA의 ORM 기술중 하나로 Proxy는 Entity를 대행하는 클래스(객체)를 뜻합니다.
Proxy는 Entity를 상속받아 런타임에 생성되는 클래스로 지연 로딩(Lazy Loading)을 사용하기 위해 사용됩니다. 

 

@Entity
public class Order {

    @Column(name = "product")
	private String product;

    //fetch 속성이 LAZY인경우 Order를 호출할때 customer를 
    //사용하지 않는다면 customer는 지연로딩이 되어 실제 사용시점에 호출됩니다.
    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;
    
}



지연로딩(Lazy Loading)이란?

특정 엔티티를 로딩할때 연관된 엔티티를 당장 쓰지 않으면 해당 엔티티를 프록시로 대체하여 실제 데이터 로딩을 지연시킵니다. 필요한 순간(즉 연관 엔티티의 값을 꺼내쓸때)만 데이터를 로드하며, 쿼리 횟수를 줄이고, 애플리케이션의 응답 시간을 최적화할 수 있습니다.

 

Proxy Class와 Entity Class 뭐가 다를까?

비교포인트\대상 Proxy Class Entity Class
생성시기 런타임에 동적으로 생성, 프록시와 연결된 엔티티가 필요한 순간에 자동 생성 개발자가 개발한 클래스로 정적으로 이미 생성되어있음
목적 지연로딩을 구현하기 위해 사용, 실제 데이터를 로딩하지 않고 필요한 순간에 데이터를 로딩하여 엔티티를 초기화함 실제 데이터를 내포하며, DB와의 매핑을 통해 영속성을 제공하고 데이터의 변경을 추적합니다.
로딩시기 지연로딩을 위해 사용, 엔티티 필드에 접근하려고할때 실제 초기화가 일어남 즉시로딩 혹은 지연로딩
클래스 유형 런타임에 동적 생성 클래스로, 개발자가 작성한 클래스의 자식 클래스로 상속해서 생성됨 개발자가 직접 작성한 클래스
사용 방식 주로 연관관계에서 지연 로딩을 구현할때 사용, 필요한 순간에만 실제 데이터를 로딩합니다. 데이터의 CRUD 작업등을 처리

 

JPA Proxy 주의사항

영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때 프록시를 초기화 하면 문제 발생 (Hibernate의 경우 org.hibernate.LazyInitalizationException 을 발생시킴)

준영속이란? 
1. 영속상태의 엔티티가 영속성 컨텍스트에서 어떠한 이유로 분리된것을 준영속 상태라고 한다. 
2. 준영속이 되는 상태는
 트랜잭션이 종료된경우,
 entityManager의 detach를 호출한경우,
 entityManager의 clear를 호출한경우,
준영속 상태의 엔티티는 영속성 컨텍스트의 관리를 받지 않기 때문에, 변경사항을 업데이트하거나, 데이터베이스에 동기화 되지 않습니다. 
준영속상태의 엔티티를 다시 영속 상태로 만들어 데이터 베이스에 동기화가능하며 entityManager의 merge메서드를 써서 가능합니다.

준영속 상태에서 호출시 에러 코드

@Service
@Transactional
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @PersistenceContext
    private EntityManager entityManager;

    public User findUserInTransaction(Long userId) {
        // 트랜잭션 범위 내에서 엔터티 조회
        return userRepository.findById(userId).orElse(null);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public String getUsernameOutsideTransaction(Long userId) {
        // 트랜잭션 범위 내에서 엔터티 조회
        User userInTransaction = userRepository.findById(userId).orElse(null);

        // 트랜잭션 종료
        entityManager.flush();
        entityManager.clear();

        // 트랜잭션이 종료된 상태에서 엔터티의 지연 로딩을 시도하면 LazyInitializationException 발생
        try {
            return userInTransaction.getPosts().get(0).getTitle(); // 예시: OneToMany 관계에서의 지연 로딩
        } catch (LazyInitializationException e) {
            return "LazyInitializationException 발생!";
        }
    }
}

관련글 더보기