Back-End/JPA

JPA - 프록시와 연관관계 관리

Meluu_ 2024. 7. 10. 17:19

 

연관관계를 맺은 Member 와 Team 객체에서

Member 만 조회할 때 Member 객체 안에는 Team 객체가 들어가 있을까?

만약 그렇다면 Member만 사용하고 싶을 때는 어떻게 해야하나 ?

JPA는 프록시 기술을 통해서 이 문제를 해결하였다. 

 

✔️ 프록시 


실제 클래스를 상속받아서 만들어진 가짜

 

 

위임

프록시 객체는 실제 객체의 참조를 보관 (Entity target 필드 가짐)

프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

 

em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

 

1. 멤버 프록시 조회

Member member = em.getRefernce(Member.class, id);

2. member.getName() 호출

member.getName();

이 순간부터 프록시는 실제 member 객체 요청을 영속성 컨텍스트에게 날린다.

영속성 컨텍스트는 해당 member를 DB에서 조회하고 실제 Entity를 생성한 뒤 

프록시가 실제 member 객체를 참조하고 (target) , 

target.getName() 을 호출한다. (위임)

 

 

프록시의 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체를 초기화할 때 프록시 객체가 실제 엔티티로 전환 X
  • 초기화되면 프록시 객체를 통해서 실제 엔티티 접근 가능
  • 프록시 객체는 원본 엔티티를 상속받음
  • 따라서 타입 체크시 주의 (== 비교 x, instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있다면 em.getReference() 호출해도 실제 엔티티 반환
  • 준영속 상태일때 프록시 초기화시 문제 발생 ( org.hibernate.LazyInitializationException )

 

            Member member = new Member();
            member3.setUsername("회원3");
            em.persist(member);

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

            Member reference = em.getReference(Member.class, member.getId());
            em.clear(); // 영속성 컨텍스트 초기화
            reference.getUsername(); // 준영속 상태에서 초기화 요청

 

참고: 프록시 객체도 영속성 컨텍스트에서 관리한다.

 

 

 

  • 프록시 확인
    • PersistenceUnitUtil.isLoaded(Object entity)
EntityManagerFactory emf;
emf.getPersistenceUnitUtil().isLoaded(reference); // false: 프록시 상태

 

  • 프록시 클래스 확인 방법
    • entity.getClass().getName() 출력 (HibernateProxy..)
jpql.jpql.Member$HibernateProxy$9IaDAX2A // 프록시라는 의미

 

  • 프록시 강제 초기화
    • org.hibernate.Hibernate.initialize(entity);

 

참고 : JPA 표준은 강제 초기화가 없기에 강제호출해야한다. getName() 등..

 

 

❓  프록시 getId() 초기화

프록시는 식별자 값을 가지고 있다.

따라서 getId()를 해도 초기화되지 않는다. 

연관관계를 설정할때 프록시를 사용해서 데이터베이스 접근 횟수를 줄일 수 있다. 

 

 

 

 

✔️ 즉시 로딩과 지연 로딩 


💠 즉시 로딩

@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩
@JoinColumn(name = "TEAM_ID")
private Team team;

 

즉시 로딩시 member 엔티티를 조회하면 JPA 구현체는 가능하면 조인을 사용해서 해당 member 엔티티와 연관된 Team까지 SQL 한 번에 조회해서 가져온다.

 

 

 

💠 지연 로딩

@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩
@JoinColumn(name = "TEAM_ID")
private Team team;

 

지연로딩시 member를 조회하면 연관관계인 team객체는 프록시 객체로 가져온다.

후에 member.getTeam().getName()등을 호출하면 그때 쿼리를 날려서 실제 엔티티를 가져온다. 

 

참고 : getObject()는 초기화 되지 않는다. 
실질적인 사용이 있어야한다. ex) getName();

 

 

 

 

프록시와 즉시로딩 주의

  • 가급적 (반드시..) 지연 로딩만 사용
  • 즉시 로딩은 JPQL에서 N+1 문제 발생
    • member와 team이 N:1로 10개씩 연관되어있다고 하자
    • 1개의 쿼리를 날려서 member를 조회했는데 즉시로딩 이기 때문에 각 member에 연관된 team을 조회하는 쿼리가 10개 생성되어 날린다. 
    • 1 + 10 즉 N+1 문제발생
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 LAZY로 설정

 

 

 

🌱 지연 로딩 활용

모든 연관관계에서 지연 로딩 사용

즉시 로딩같은 기능이 필요하다면 JPQL의 fetch 조인, 엔티티 그래프 기능을 사용

 

 

 

✔️ 영속성 전이 CASCADE


특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때

 

ex) 부모저장시 자식도 함께 저장

@OneToMany(mappedBy="parent", cascade = CascadeType.PERSIST)

 

테스트

Parent parent = new Parent();
Child child = new Child();
parent.getChildList().add(child);
em.persist(parent);
em.flush();
em.clear();

연관관계 매핑과 관련 없음

 

영속성 전이는 연관관계를 매핑하는 것과 아무 관련 없다

엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공

 

 

 

 

CASCADE 종류

  • ALL : 모두 적용
  • PERSIST : 영속
  • REMOVE : 삭제
  • MERGE : 병합
  • REFRESH : REFRESH
  • DETACH : DETACH

 

 

✔️ 고아 객체


고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동 제거

 

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
orphanRemoval = true // 고아 객체 제거

 

Parent parent = new Parent();
Child child = new Child();
parent.getChildList().add(child);
child.setParent(parent);
em.persist(parent);
em.flush();
em.clear();

Parent parent1 = em.find(Parent.class, parent.getId());
parent1.getChildList().remove(0);

고아 객체 삭제

 

 

주의 사항

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일 때 사용
  • 특정 엔티티가 개인 소유할 때 사용
  • @OneToOne, @OneToMany만 가능

 

 

참고 : 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작

 

 

 

✔️ 영속성 전이 + 고아 객체, 생명주기


cascade = CascadeType.ALL, orphanRemoval = true

 

스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거

부모 엔티티가 자식의 생명주기를 관리하는 형태

도메인 주도 설계의 Aggregate Root 개념을 구현할 때 유용

 

 

 

 

 

 

ps. 도메인 주도 설계 책을 읽어봐야겠다.

 

 

🔖 학습내용 출처


자바 ORM 표준 JPA 프로그래밍 - 기본편 / 김영한

 

김영한. (2021). 자바 ORM 표준 JPA 프로그래밍. 에이콘출판사

'Back-End > JPA' 카테고리의 다른 글

JPA - JPQL  (0) 2024.07.12
JPA - 값타입  (0) 2024.07.12
JPA - 고급 매핑  (0) 2024.07.09
JPA - 1:N 단방향과 양방향에 대해서  (0) 2024.07.09
JPA - 다양한 연관관계 매핑  (0) 2024.07.09