프록시
Team에 속해있는 Member 를 조회할 시 Team까지 조회할 필요가 있을까?
비지니스 로직에서 필요하지 않을 때가 있는데 항상 Team을 함께 조회한다면 낭비가 발생된다.
이 낭비를 하지 않기 위해 프록시라는 갠며으로 해결한다.
- 실제 클래스를 상속 받아서 만들어지기 때문에 겉 모양이 같다
- 사용하는 입장에서는 진짜, 가짜 객체인지 구분하지 않고 사용하면 된다.
- 프록시 객체는 실제 객체의 참조를 보관 -> 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
// 호출한 순간 쿼리 실행
Member findMember = em.find(Member.class, member.getId());
// 호출했을 때 실행 안함
Member findMember2 = em.getReference(Member.class, member.getId());
// 이때 쿼리 실행 함 -> 실제 필요한 시점에 쿼리 실행
System.out.println("findMember = " + findMember.getName());
프록시 객체의 초기화
Client에서 Member의 이름을 요청했을 때 영속성 컨텍스트에 제일 먼저 초기화를 요청한 후 영속성 컨텍스트가 실제 Entity를 만든것을 사용해서 초기화를 한다. 그 이후로 이름을 요청했을 때 taget으로 되어있는 Member를 조회하기 때문에 다시 DB조회를 하지 않아도 된다.
프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님 -> 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 ( == 비교 대신 instance of 사용 )
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.find(Member.class, member2.getId());
Member reference1 = em.getReference(Member.class, member1.getId());
Member reference2 = em.getReference(Member.class, member2.getId());
// true
System.out.println("m1 == m2 : "+(m1.getClass() == m2.getClass()));
// false
System.out.println("reference1 == reference2 : " +(m1.getClass() == reference2.getClass()));
// true
System.out.println("reference1 == reference2 : " +(m1 instanceof Member));
// true
System.out.println("reference1 == reference2 : " +(m2 instanceof Member));
파라미터로 프록시로 들어올지 실제로 들어올지 모르기 때문에 == 비교는 절때 사용하지 말것
타입을 비교할 때는 꼭 instanceof 를 사용해라!
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getreference()를 호출해도 실제 엔티티가 반환한다.
// m1.getClass -> 실제 클래스 반환
Member m1 = em.find(Member.class, member1.getId());
// m1.getClass -> 실제 클래스 반환
Member reference = em.getReference(Member.class,member1.getId());
* 한 영속성 컨텍스트에서 호출했고, PK가 같다고 한다면 이미 영속성 컨텍스트에 있는 객체를 프록시로 반환해봤자 의미가 없기 때문에 실제 클래스를 반환한다.
* JPA는 하나의 영속성 컨텍스트에서 조회하는 같은 엔티티의 동일성을 보장한다.
반대로 아래와 같이 proxy 객체를 먼저 호출하고 실제 객체를 호출하게 된다면?
// m1.getClass -> Proxy 객체 반환
Member reference = em.getReference(Member.class,member1.getId());
// m1.getClass -> Proxy 객체 반환
Member m1 = em.find(Member.class, member1.getId());
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제점
-> clear 후에 getName을 조회하면 not initialize proxy 에러가 뜬다.( 더이상 컨텍스트의 도움을 받지 못함)
Member reference1 = em.getReference(Member.class, member1.getId());
System.out.println(reference1.getClass());
em.clear();
System.out.println(reference1.getName());
프록시 확인
- 프록시 인스턴스의 초기화 여부 확인
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
PersistenceUnitUtil persistenceUnitUtil = emf.getPersistenceUnitUtil();
Member reference1 = em.getReference(Member.class, member1.getId());
System.out.println(reference1.getName());
// true
System.out.println(persistenceUnitUtil.isLoaded(reference1));
- 프록시 클래스 확인 방법
entitiy.getClas().getName() 출력
- 프록시 강제 초기화
해당 entity를 강제 초기화 한다.
Hibernate.initalize(member);
즉시 로딩과 지연 로딩
* Member와 Team에서 Team은 잘 사용하지 않는다 했을 때
지연 로딩 LAZY을 사용해서 프록시로 조회
Member 와 ManyToOne 관계에 있는 Team을 조회할 때 프록시를 사용하겠다. 라고 정의 하는 것이다.(Member만 DB에서 조회)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
- 아래의 그림 처럼 Member만 조회 했을 때 연관관계에 있는 Team은 조회하지 않고 Member 만을 조회한다. (Team는 가짜인 proxy객체가 있어서 조회되지 않음)
- 만약 Member에서 Team 관련 조회를 했을 때 그 시점에서 초기화가 된다( DB에서 직접 조회 )
* Member와 Team에서 자주 함께 사용한다면?
즉시 로딩 EAGER를 사용해서 함께 조회 ( Proxy를 사용하지 않는다 )
- 가능하면 JPA는 조인으로 한번에 조회한다.
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
* 주의할점
- 가급적 지연 로딩만 사용 한다. -> Member와 Team을 같이 조회하고 싶다면 JPQL fetch join이나 엔티티 그래프 기능으로 해결한다.
- 즉시 로딩을 적용하면 예상하지 못한 SQL 발생
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
- @ManyToOne, @OneToOne 은 기본이 즉시 로딩 --> LAZY로 설정
- @OneToMany, @ManyToMany 는 기본이 지연 로딩
영속성 전이 : CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
-> 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장한다.
아래와 같이 연관관계 편의 메서드를 만든다.
@Entity
public class Parent{
@OneToMany(mappedBy="parent",cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){
childList.add(child);
child.setParent(this);
}
여기서 cascade를 걸어주면 Parent를 persist할 때 Child도 같이 persist 해준다.
주의 ) 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음!!!
그저 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화 시키는 편리함을 제공할 뿐이다.
CASCADE의 종류
- ALL - 모두 적용 * 해당 엔티티와 라이프사이클이 같을 때 사용한다 -> 해당 객체가 다른곳에서도 사용될 때 사용 X
- PERSIST - 영속 * 저장할 때만 쓸게 (삭제에 대해 조심할 때)
- REMOVE - 삭제
등 다른 종류도 있지만 보통 ALL or PERSIST를 사용한다.
고아 객체
고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
Cascade 와 orphanRemoval를걸어준다.
@OneToMany(mappedBy="parent",cascade = CascadeType.ALL, orphanRemoval=true)
로직에서 Parent 의 Child List 에서 선택한 Child 요소를 remove 하면 DB에서도 삭제가 된다.
( 컬렉션에서 remove 하는데도 DB에서 같이 사라짐)
* 주의 *
- 참조하는 곳이 하나일 때 사용해야함!
- 특정 엔티티가 개인 소유할 때 사용
- @OneToOne, @OneToMany만 가능하다.
- CascadeType.Remove처럼 작동한다.
영속성 전이 + 고아 객체, 생명주기
둘다 사용하게 되면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.
예를 들어서 부모 엔티티가 Repository를 이용하여 삭제를 했을 때 자식은 Repository를 만들 필요 없이 지워지게 된다.
'WEB > JPA' 카테고리의 다른 글
Hibernate 에서 제공하는 @NotNull & @NotEmpty & @NotBlank 사용하기 (0) | 2022.08.16 |
---|---|
[Querydsl] 내가 찾아 쓰려고 정리한 글 (0) | 2022.03.15 |
6. 자바 ORM 표준 JPA 프로그래밍 - 고급매핑 (0) | 2021.05.17 |
5. 자바 ORM 표준 JPA 프로그래밍 - 다양한 연관관계 매핑 (0) | 2021.05.14 |
4. 자바 ORM 표준 JPA 프로그래밍 - 연관관계 (0) | 2021.05.14 |