반응형
프록시란?
- 대리자라는 뜻으로, 클라이언트가 사용하려고 하는데 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 역할을 말한다.
- 프록시는 실제 대사인 것처럼 위장함으로서 이를 사용하는 클라이언트는 구체 클래스를 알 필요가 없어진다.
- 또한 프록시 클라이언트의 요청을 받아서 원래 요청 대상에게 바로 넘겨주는 게 아닌, 다양한 부가기능을 지원할 수 있다.
- 여기서 원래 요청하려는 대상 즉, 최정적으로 요청을 위임받아 처리하는 실제 오브젝트를 타깃 이라한다.
프록시의 조건
- 클라이언트의 요청을 대리로 수행해주는 모든 객체가 프록시 인것은 아니다.
- 객체가 프록시가 되려면 클라이언트는 요청을 보낸 대상이 타깃인지 프록시 인지 구분을 할 수 없어야한다. 즉, 타깃과 프록시는 같은 인터페이스를 확장해야한다. (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' 카테고리의 다른 글
Java 정리 (0) | 2024.12.08 |
---|---|
[JPA] 값 타입, 임베디드 타입 (0) | 2022.11.22 |
Eclipse Svn 설치 (0) | 2022.10.28 |
Eclipse에 Azure 설치 하기 (0) | 2022.10.27 |
엔티티의 생명주기 (0) | 2022.10.10 |