Language/Java

프록시와 연관관계 관계

JUNGKEUNG 2022. 11. 16. 23:30

프록시란?

  • 대리자라는 뜻으로, 클라이언트가 사용하려고 하는데 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 역할을 말한다.
  • 프록시는 실제 대사인 것처럼 위장함으로서 이를 사용하는 클라이언트는 구체 클래스를 알 필요가 없어진다.
  • 또한 프록시 클라이언트의 요청을 받아서 원래 요청 대상에게 바로 넘겨주는 게 아닌, 다양한 부가기능을 지원할 수 있다.
  • 여기서 원래 요청하려는 대상 즉, 최정적으로 요청을 위임받아 처리하는 실제 오브젝트를 타깃 이라한다.

프록시의 조건

  • 클라이언트의 요청을 대리로 수행해주는 모든 객체가 프록시 인것은 아니다.
  • 객체가 프록시가 되려면 클라이언트는 요청을 보낸 대상이 타깃인지 프록시 인지 구분을 할 수 없어야한다. 즉, 타깃과 프록시는 같은 인터페이스를 확장해야한다. (CGLib처럼 구현 클래스를 상속받는 방법도 있다.) 이로써 느슨한 연결을 유지하며, OCP원칙을 통해 좋은 코드를 작성할 수 있다.

  • m.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

 

 

프록시 특징

  • 실제 클래스를 상속 받아서 만들어진다
  • 실제 클래스와 겉 모양이 같다
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다. (이론상)
  • 상속 관계는 부모 관계를 보고 사용하기 때문에 어디까지나 이론상이다.
  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

 

2. 프록시의 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화한다.
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (==비교 실패, 대신 instance of사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시 초기화하면 문제 발생
  • (하이버네이트는 org.hibernate.LazyINitializationException 예외를 터트림)
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

Member member2 = new Member();
member2.serUsername("member2");
em.persist(member2);

Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, membergetId());

System.out.println("m1 == m2: " + (m1.getClass() == m2.getClass()));
m1 == m2 :false 

private static void logic(Member m1, Member m2) { 104
 
	System.out.println("m1 == m2: " + (m1 instanceof Member));
	System.out.println("m1 == m2: " + (m2 instanceof Member));
}

m1 == m2: true
m2 == Member : true
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass()); //Proxy

Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getClass()); //Member

System.out.println("refMember == findMember: " (refMember == findMember));

---------------------------------------------
refMember = class hellojpa.Member$HibernateProxy$L41zv52Q
findMember = class hellojpa.Member$HibernateProxy$L41zv52Q
refMember == findMember: true

프록시가 한번 호출되면 그 다음 프록시에도 영향이 가는데 중복되는 값이 저장이 되는경우가 있

 

 

프록시 확인

프록시 인스턴스의 초기화 여부 확인

  • PersistenceUnitUtil.isLoaded(Object entity)

프록시 클래스 확인 방법

  • entity.getClass().getName() 출력(..javasist.. or HibernateProxy..)

프록시 강제 초기화

  • or.hibernate.Hibernate.initialize(entity);

참고 : JPA 표준은 강제 초기화 없음

강제 호출 : mebmer.getName()

Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass()); //Proxy
refMEmber.getUsername(); // 강제 초기화 

// 하이버네이트에서 제공해주는 iHibernate.nitialize 사용해보자
Hibernate.initialize(refMember); // 강제 초기화 

프록시 정리

특정 객체에 대한 접근을 제어하거나 기능을 추가 할 수 있는 객체를 의미

 

 

장점

  • OCP : 기존 코드를 변경하지 않고 새로운 기능을 추가 할 수 있다
  • SRP : 기존 코드가 해야 하는 일만 유지할 수 있다
  • 기능 추가, 접근 제어 등 다양하게 응용하여 활요 할 수 있다

단점

  • 코드의 복잡도가 증가한다
  • 중복 코드가 발생한다

 

즉시 로딩과 지연 로딩

지연 로딩 LAZY을 사용해서 프록시로 조회

@Entity
public class Member {

	@Id
	@GeneratedValue
	private Lond id;

	@Column(name = "USERNAME")
	private String name;

	//지연 로딩 LAZY을 사용해서 프록시로 조회
	@ManyToOne(fetch = FatchType.LAZY)
	@JoinColumn(name = "TEAM_ID")	private Team team;

}

지연 로딩을 사용하면 연관된것을 proxy로 가져오게 된다.

@Entity
public class Member {

	@Id
	@GeneratedValue
	private Long id;
	
	@Column(name = "USERNAME")
	private String name;
	
	// 즉시 로딩 EAGER를 사용해서 함께 조회
	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "TEAM_ID")
	private Team team;

}

  • Member와 Team을 같이 사용하는데 Member를 80% 정도 호출하고 Team은 가끔씩 호출을 한다면 평소에는 두개다 호출이 되기 때문에 필요없는 데이터 값이 들면서 로딩 시간도 길어지지만 지연 로딩(LAZY) 사용하면 Team은 나중에 필요할때 나중에 호출이 된다.
  • 하지만 Member와 Team 두개다 비슷하게 호출이 되면 즉시로딩(EAGER)를 사용하여 바로 호출 할수 있도록 한다.
  • EAGER를 사용하면 두 가지 방법으로 호출할수가 있는데 Join을 사용하여 한번에 호출하거나 아니면 하나씩 호출 할수가 있다. 대부분이 Join을 이용하여 호출한다.

 

 

프록시와 즉시로딩 주의

  • 가급적 지연 로딩만 사용(특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
    • 지금은 묶인것이 2개 뿐이지만 만약 10개, 100개가 된다면 한번에 많은 쿼리를 가져오게 되고 예상하지 못한 SQL문제가 발생하면 찾기가 힘들어 진다
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다
    • 지연 로딩을 사용하면 사용하지 않는 것을 호출하지 않는다
    • 즉시 로딩은 한번에 다 가녀오기 때문에 N개 만큼 문제가 일어난다
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩 → LAZY로 설정
  • @OneToMany, @ManyToMany는 기본이 지연 로딩이다.

createQuery 는 쿼리를 Jpa에서 직접 코드를 작성하여 쿼리를 만드는 방법이다

SQL : select * from MEmber

SQL : select * from Team where TEAM_ID = xxx

지연 로딩과 즉시 로딩 활용

  • Member와 Team은 자주 함께 사용하면 → 즉시 로딩(Eager)
  • Member와 Order는 가끔 사용 → 지연 로딩(LAZY)
  • Order와 Product는 자주 함께 사용 → 즉시로딩(Eager)

 

영속성 전이 : CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들도 싶을 때
    • 예) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장

주의점

  • 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.

 

CASCADE의 종류

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

게시판이나 첨부파일 같은곳에 많이 사용된다.

 

 

고아 객체

  • 고아 객체 제거 : 부모 엔티티와 연관관계가 귾어진 자식 엔티티를 자동으로 삭제
  • orphanRemoval = true
  • Parent prrent1 = e..find(Parent.calss, id)
  • 자식 엔티티를 컬렉션에서 제거

 

고아 객체 - 주의

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

 

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

  • CascadeType.ALL + orphanRemovel= true
    • 연결할 객체에 작성하면 REmove되어 자동으로 삭제가 된다
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용

'Language > Java' 카테고리의 다른 글

[JPA] 값 타입, 임베디드 타입  (0) 2022.11.22
Eclipse Svn 설치  (0) 2022.10.28
Eclipse에 Azure 설치 하기  (0) 2022.10.27
엔티티의 생명주기  (0) 2022.10.10
긴 매개변수 목록  (0) 2022.08.15