대리, 대신하다
클라이언트(나) → 프록시(부동산) → 타킷(집주인)
사용 목적에 따라 두 가지로 구분할 수 있다.
JPA 프록시
@Transactional
JPA에서 사용하는 프록시는 실제 객체를 상속한 타입을 가지고 있습니다.
그래서 실제 객체처럼 행동합니다.
따라서 프록시 객체는 실제 엔티티로 바뀌는 것이 아니라
초기화 되면서 프록시 객체를 통해서 실제 엔티티에 접근이 가능한 것입니다.
또한 상속했기 때문에 흑구 멘토님이 말씀해주셨던
에 대한 답도 구할 수 있습니다.
record는 불변하여 setter를 가질 수 없습니다.
정리해보면 Bealdulg에서는 그 이유에 대해서
Proxies are classes that are generated at runtime and extend the entity class. These proxies rely on the entity class to have a no-args constructor and setters. Since records don't have these, they can't be used as entities.
프록시 객체는 런타임 때 생성되며 엔티티 클래스를 상속받는다.
이 프록시는 기본 생성자와 setter를 가지고 있는 엔티티 클래스에 의존한다.
레코드는 이것들을 가지고 있지 않기 때문에 그들은 엔티티로 사용될 수 없다
Hibernate uses proxy objects to allow lazy loading.
바로 지연로딩( Lazy Loading) 위해서 사용합니다.
지연 로딩은 왜 사용할까요? 🤔
하나의 예시를 떠올려 봅시다.
주문은 여러 주문아이템을 가지고 있고
하나의 오더아이템는 여러의 주문에 포함된
ManyToOne과 OneToMany의 상황이라고 가정합시다.
비즈니스 로직에서 오더아이템을 불러올 때 이 오더가 필요한 상황이 많지 않습니다.
그렇다고 이 오더에 null 값을 넣어둘 수는 없으니 뭔가 넣어둬야 하니 이 연관관계 자리에 프록시를 넣어두는 것입니다.
예시는 오더아이템을 호출하는 상황입니다.
프록시 객체는 실제 값이 필요할 때 DB에 값을 요청하게 된다.
프록시 객체는 처음 사용할 때 한번만 초기화 된다. 이후에는 영속성 컨텍스트에 있는 엔티티를 계속 가져와서 사용한다.
정답
프록시는 실제 엔티티를 상속받은 객체이기 때문에 저기 매개변수로 들어온 m1과 m2가 프록시인지 아닌지 알기 어려운 상태이며
이 때 == 비교를 하면 겉보기에는 같은 엔티티이기 때문에 원하지 않은 결과가 나올 수 있으므로 instanceof를 사용해야 한다.
@Test
void orderItem_EqualsTest() {
//given
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//when
transaction.begin();
OrderItem orderItem1 = new OrderItem(1L, 1000, 10);
OrderItem orderItem2 = new OrderItem(2L, 1000,20);
em.persist(orderItem1);
em.persist(orderItem2);
em.flush();
em.clear();
OrderItem findOrderItem = em.find(OrderItem.class, 1L); //
OrderItem referenceOrderItem = em.getReference(OrderItem.class, 2L);// 프록시생성해주는 함수
System.out.println(findOrderItem.getClass() == referenceOrderItem.getClass());
transaction.commit();
}
정답 false
``` System.out.println(findOrderItem instanceof OrderItem); System.out.println(referenceOrderItem instanceof OrderItem); ```
true일까 false일까? reference에는 프록시가 저장될까?
@ActiveProfiles("test")
@SpringBootTest
public class RelationMappingTest {
@Autowired
EntityManagerFactory entityManagerFactory;
@Test
@DisplayName("Order와 OrderItem 매핑이 잘 되어있는지 확인한다.")
void order_orderItem_mappingTest() {
//given
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//when
transaction.begin();
OrderItem orderItem = new OrderItem(1L, 1000, 10);
String uuid = UUID.randomUUID().toString();
Order order = new Order(uuid, "---");
orderItem.setOrder(order);
em.persist(orderItem);
em.flush();
em.clear();
System.out.println("==========find=============");
OrderItem orderItem1 = em.find(OrderItem.class, 1L); // 영속성 컨텍스트
System.out.println("========getReference===============");
OrderItem reference = em.getReference(OrderItem.class, 1L); // 프록시 -> 영속성
System.out.println("=======================");
System.out.println(orderItem1 == reference);
transaction.commit();
}
}
정답 true
System.out.println("order:"+orderItem1.getOrder().getClass().getName()); System.out.println("orderItem1 :"+orderItem1.getClass().getName()); System.out.println("reference:"+reference.getClass().getName());
영속성 컨텍스트에 있는데 굳이 프록시 객체를 만들어야할까?
영속성 컨텍스트에 있으면 실제 엔티티를 반환한다.
한 영속성 컨텍스트에서 가져오고 기본키가 같으면 항상 ==비교에서 true를 반환해야 한다.
다시 find와 getReference의 순서를 바꿔보자
System.out.println("========getReference===============");
OrderItem reference = em.getReference(OrderItem.class, 1L); // 프록시, 영속성 컨텍스트에 아무것도 없다
System.out.println("==========find=============");
OrderItem orderItem1 = em.find(OrderItem.class, 1L); // 근데 왜 이 아이가 프록시를 향할까?
System.out.println("=======================");
System.out.println(orderItem1 == reference);
true일까 false일까?
정답 true
System.out.println("order:"+orderItem1.getOrder().getClass().getName()); // lazy 로딩 System.out.println("orderItem1 :"+orderItem1.getClass().getName()); // entity System.out.println("reference:"+reference.getClass().getName()); // vmf
영속성 컨텍스트에 없을 때는 프록시 객체를 만들어서 반환합니다.
그러나 위 상황에서 왜 실제 entity가 프록시 객체의 주소를 가지게 되는데 왜?
동일성 보장을 해주기 위해서 한 트랜잭션 내에서 최초 생성이 프록시로 된 엔티티는 이후 초기화 여부에 상관없이 영속성 컨텍스트가 무조건 같은 프록시 객체를 반환합니다.