[JPA] 프록시
엔티티 객체를 조회할때는 사용범위를 고려해야한다. 어떤 엔티티 객체가 FK 컬럼으로 다른 엔티티를 참조하고 있다면 이 참조는 즉시 불러와질까, 아니면 어떻게 될까?
앞서 흔하게 사용한 em.find()는 즉시 모든걸 가져온다. 데이터베이스를 통해서 실제 엔티티 객체를 가져온다는 뜻이다.
반면에 EntityManager에는 .getReference()라는 것이 존재하는데, 이는 데이터베이스에서 조회하는 쿼리를 날리지 않고 가짜 엔티티 객체를 가져오는 메서드이다. 간단하게 말하면 가져왔다고 가정은 되어 있지만 실제로 쿼리를 날려서 가져온 것이 아니고 쉽게 말해 null이 들어가 있는 상태이다.
프록시는 실제 클래스를 상속 받아서 만들어진 존재이고 실제 클래스와 겉모양이 같다. 이 프록시는 프록시 객체, 즉 가짜 엔티티에서 .getName()과 같은 메서드를 호출시에 영속성 컨텍스트에 초기화 요청을 하게 된다. 그러면 영속성 컨텍스트는 DB에서 조회하게 되고 실제 엔티티를 만든다.
특징
1. 이 프록시 객체는 처음 사용할때만 초기화되고 그 뒤로는 캐시에 저장된다. 프록시 객체를 불러왔다고 해서 실제 엔티티로 바뀌는 것은 아니고 프록시 객체를 동해서 실제 엔티티에 접근이 가능하게 해주는 메커니즘이다. 한번 프록시는 실제 엔티티를 조회하고 나서도 .getClass()로 찍어봐면 똑같이 프록시를 리턴한다. 고로 타입 체크할 때는 ==비교가 아니고 instance of 를 사용해서 비교하는게 옳다.
2. 영속성 컨텍스트 캐시에 이미 엔티티가 있으면 getReference()로 호출해도 실제 엔티티를 반환하게 된다. 여기에는 두가지 이유가 있는데, 캐시에 있는 원본 반환하는게 최적화 입장에서 좋다. 더 중요한 이유는, JPA에서는 == 비교시에 true를 반환해야하는 JPA의 메커니즘은 한 트랜잭션 안에서 같은 것에 대해서 보장해주는 것이기 때문에 마치 자바 컬렉션처럼 사용할 수 있게 구현되어 있기 때문이다.
3. 영속성 컨텍스트에 포함되지 않는(detach, close) 준영속 상태일때는 프록시를 초기화하면 LazyInitializationException예외가 발생한다.
프록시가 초기화가 되었는지 확인하는 방법은,
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(m2));
프록시를 초기화 하는 방법 ※JPA 표준에는 강제 초기화는 없다. Hibernate에서 제공하는 것임.
m2.getUsername(); // 강제 초기화1
Hibernate.initialize(m2); // 강제 초기화2