JPA 프록시

byeol·2023년 9월 28일
0

Proxy

대리, 대신하다
클라이언트(나) → 프록시(부동산) → 타킷(집주인)

사용 목적에 따라 두 가지로 구분할 수 있다.

  • 클라이언트가 타킷에 접근하는 방법을 제어하는 것 → JPA 프록시
  • 타깃에 부가적인 기능을 부여해주는 것 → @Transactional

JPA에서 사용하는 프록시는 실제 객체를 상속한 타입을 가지고 있습니다.

그래서 실제 객체처럼 행동합니다.

따라서 프록시 객체는 실제 엔티티로 바뀌는 것이 아니라

초기화 되면서 프록시 객체를 통해서 실제 엔티티에 접근이 가능한 것입니다.

또한 상속했기 때문에 흑구 멘토님이 말씀해주셨던

record가 entity가 될 수 없는 이유가 무엇인가?

에 대한 답도 구할 수 있습니다.

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에 값을 요청하게 된다.

    • 그리고 그렇게 불러온 엔티티에 대한 정보가 영속성 컨텍스트에 저장된다.
  • 프록시 객체는 처음 사용할 때 한번만 초기화 된다. 이후에는 영속성 컨텍스트에 있는 엔티티를 계속 가져와서 사용한다.

Q1 :** • private static void logic(Member m1, Member m2) 어떻게 타입 체크를 할 것인가?

정답
프록시는 실제 엔티티를 상속받은 객체이기 때문에 저기 매개변수로 들어온 m1과 m2가 프록시인지 아닌지 알기 어려운 상태이며
이 때 == 비교를 하면 겉보기에는 같은 엔티티이기 때문에 원하지 않은 결과가 나올 수 있으므로 instanceof를 사용해야 한다.

Q2** :그렇다면 아래 상황의 결과는 true일까 false일까?

    @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);
```

Q2**: JPA는 1개의 트랜잭션에서 같은 id로 조회한 엔티티와 프록시는 ==비교가 가능하도록 1개 타입만 반환한다?

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가 프록시 객체의 주소를 가지게 되는데 왜?
동일성 보장을 해주기 위해서 한 트랜잭션 내에서 최초 생성이 프록시로 된 엔티티는 이후 초기화 여부에 상관없이 영속성 컨텍스트가 무조건 같은 프록시 객체를 반환합니다.

profile
꾸준하게 Ready, Set, Go!

0개의 댓글