[JPA] 프록시와 연관관계 매핑

hwarye·2023년 1월 11일
0

JPA

목록 보기
5/5

목차

1) 프록시
2) 즉시 로딩과 지연 로딩
3) 지연 로딩 활용
4) 영속성 전이 : CASCADE + 고아 객체

Hibernate 방식의 설명
=> JPA 표준 명세는 지연 로딩 구현을 JPA 구현체에 위임.
=> Hibernate의 지연 로딩 지원 방식
1) 프록시 사용
2) 바이트코드 수정 -> 설명 복잡


🟡 프록시

  en.wikipedia.org/wiki/Proxy_pattern
  • 실제 엔티티 객체 대신, 데이터베이스 조회를 지연할 수 있는 가짜 객체

    //JPA에서 식벽자로 엔티티 하나 조회 할 때 EntityManager.find() 사용.
    //em.find()는 영속성 컨텍스트에 엔티티가 없으면 데이터베이스를 조회.
    //-> 조회한 엔티티를 실제 사용 여부와 관계 없이 데이터베이스를 조회하게 됨. (quary 실행)
    
    Member member = em.find(Member.class, "member1")
    
    --------------------------------------------------------
    
    // 때문에 실제 사용 시점까지 데이터베이스 조회를 미루기 위해 EntityManager.getReference() 사용
    // 이 메소드는 JPA에 데이터베이스를 조회하지 않고 실제 엔티티 객체도 생성하지 않음.
    // 데이터베이스 접근을 위임한 프록시 객체 반환. (quary 실행 x)
    
    Member member = em.getReference(Member.class, "member1")
  • 지연 로딩 기능을 사용하기 위함

  • 실제 클래스를 상속 받아 만들어지기 때문에 실제 클래스와 겉 모양이 같음.
    -> 사용자 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨.

  • 프록시 위임 (delegate)

    실제 객체에 대한 참조(target) 보관.
    프록시 객체의 메소드 호출할 경우, 프록시 객체는 실제 객체의 메소드 호출.

🫧 프록시 객체의 초기화

실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는 것.
ex) Member.getName()

    //MemberProxy 반환
    Member member = em.getReference(Member.class, "id1");
    member.getName();

➕ 프록시 객체 초기화 순서

1) 프록시 객체에 member.getName()을 호출하여 실제 데이터 조회.
2) 실제 엔티티가 생성되어 있지 않을 경우 프록시 객체가 영속성 컨텍스트에 실제 엔티티 생성을 요청. (= 초기화)
3) 영속성 컨텍스트는 데이터베이스를 조회하여 실제 엔티티 객체 생성.
4) 생성된 실제 엔티티 객체 참조를 프록시 객체가 Member target 멤버 변수에 보관.
5) 실제 엔티티 객체의 getName()을 프록시 객체가 호출하여 결과 반환.

🫧 프록시 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화됨.
  • 프록시 객체가 초기화될 경우, 프록시 객체를 통해 실제 엔티티로 접근이 가능.
    프록시 객체가 실제 엔티티로 바뀌는 것은 아님.
  • 원본 엔티티를 상속 받은 객체이기 때문에 타입 체크 시에 주의해서 사용해야 한다.
    따라서 타입 체크 시 == 비교가 아닌 instanceof 사용해야 한다.
Member memberA = new Member();
memberA.setUsername( "memberA" );

Member memberB = new Member();
memberB.setUsername( "memberB" );

Member memberC = new Member();
memberB.setUsername( "memberC" );

em.persist( memberA );
em.persist( memberB );
em.persist( memberC );

em.flush(); // 영속성 컨텍스트 내용 DB에 반영하기
em.clear(); // 영속성 컨텍스트 초기화 (SELECT 쿼리를 보기 위함)

//⭐️
Member findMemberA = em.find( Member.class, memberA.getId() );

Member findMemberB = em.find( Member.class, memberB.getId() );
System.out.println( "findMemberA == findMemberB " + ( findMemberA.getClass() == findMemberB.getClass() ) );

//⭐️
Member referenceMemberC = em.getReference( Member.class, memberC.getId() );
System.out.println( "findMemberA == referenceMemberC " + ( findMemberA.getClass() == referenceMemberC.getClass() ) );
System.out.println( "referenceMemberC instanceof Member " + ( referenceMemberC instanceof Member ) );

System.out.println( "findMemberA Class: " + ( findMemberA.getClass() ) );
System.out.println( "referenceMemberC Class: " + ( referenceMemberC.getClass() ) );

tx.commit();

  • 영속성 컨텍스트가 찾는 엔티티가 이미 존재할 경우 데이터베이스를 조회할 필요가 없기 때문에 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.

  • 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다.
    때문에 준영속 상태의 프록시(영속성 컨텍스트의 도움을 받을 수 없는 상태의 프록시)를 초기화하면 문제가 발생하며,
    하이버네이트는 org.hibernate.LazyInitializationException 예외를 발생시킨다.

🫧 준영속 상태와 초기화

//MemberProxy 반환
Member member = em.getReference(Member.class, "id1");

transaction.commit();

em.close() //영속성 컨텍스트 종료

member.getName(); // 준영속 상태 초기화 시도.

//-> org.hibernate.LazyInitializationException 예외 발생

em.close()로 준영속 상태
-> member.getName()을 호출 -> 초기화 시도 -> 영속성 컨텍스트가 없으므로 실제 엔티티 조회 불가 -> 예외 발생.

JPA 표준 명세는 지연 로딩(프록시)에 대한 내용을 JPA 구현체에 맡겼음.
때문에 준영속 상태의 엔티티를 초기화할 때 어떤 일이 발생하는 지 표준 명세에 정의되어 있지 않음.
Hibernate는 org.hibernate.LazyInitializationException 예외 발생.

즉시 로딩과 지연 로딩

지연 로딩 : 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법


지연 로딩 활용


영속성 전이(CASCADE) + 고아 객체


🟡 proxy pattern

Proxy Pattern 이란?

프록시는 대리인이다. 프록시 패턴 또한 마찬가지이다.
프록시에게 어떤 일을 대신 시키는것.
간단하게 말해서, 최고참에게 물어보기 전에 사수한테 먼저 물어보는 방식.

구체적으로 인터페이스를 사용하고 실행시킬 클래스에 대한 객체가 들어갈 자리에 프록시(대리자) 객체를 대신 투입.
클라이언트 입장에선 실제 실행시킬 클래스 객체의 메서드를 호출하고 반환값을 받는지, 대리자 객체의 메서드를 호출하고 반환값을 받는지 전혀 모르게 처리하는 것이다.

Proxy Pattern의 구현방법

1) 프록시(대리자)는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.
2) 프록시는 실제 서비스에 대한 참조 변수를 갖는다.
3) 프록시는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 값을 클라이언트에게 돌려준다.
4) 프록시는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수 있다.

다만 결과값을 조작하거나 변경하면 안된다.
그저 제어의 흐름을 변경하거나 다른 로직을 수행할 뿐이다.

즉, 클라이언트가 어떤 일에 대한 요청(실제 서비스의 메서드 호출)을 하면,
Proxy가 대신 실제 서비스의 메서드를 호출하고 그 반환값을 클라이언트에게 전달한다.

interface.java
// 실제와 프록시를 연결해주는 인터페이스
public interface IService {
	void requestApi();
}Service.java
//실제 서비스
public class Service implements IService {
	
    @Override
    public void requestApi() {
    //실제로 동작하는 곳
    }
}Proxy.java
//실제 서비스의 작업을 대신해 줄 proxy
public class Proxy implements IService {
	
    private IService mService;
    
    @Override
    public void requestApi(){
    	//이전 작업..
        
        //실제 서비스의 객체를 생성하고 메서드 호출
        mService = new Service();
        mService.requestApi();
        
        //이후 작업..
    }
}Client.java
//클라이언트
public class Client {
	public static void main(final String[] args) {
    	//실제 서비스를 직접 호출하는 것이 아닌 Proxy 호출
        final Proxy proxy = new Proxy();
        proxy.requestApi();
    }
}


Ref)

profile
례코드

0개의 댓글