프록시
em.find() vs em.getReference()
- em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회 → DB에 쿼리 안 나감
프록시 특징
- 실제 클래스를 상속 받아서 만들어짐
- 실제 클래스와 겉 모양이 같다.
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨
- 프록시 객체는 실제 객체의 참조(target)를 보관
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
프록시 객체의 초기화
//프록시 객체 조회
Member findMember = em.getReference(Member.class, member.getId());
//내부적으로 영속성 컨텍스트에 요청
member.getName();
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
// 이미 Id는 있어서 DB에서 안 가져와도 됨
System.out.println("findMember.username = " + findMember.getUsername());
// 이 시점에 DB에 쿼리를 날림
프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해 실제 엔티티에 접근 가능
- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의
- == 비교 X!! (m1.getClass() == m2.getClass())
- (m1 instanceof Member), (m2 instanceof Member)
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
- → em.getReference 후 em.find 하면 둘 다 프록시 반환 == 비교 맞추기 위해
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
- → 영속성 컨텍스트의 도움을 받기 전에 em.detach(refMember), em.close(), em.clear() 할 때 → 프록시 초기화 못 함
프록시 확인
- 프록시 인스턴스의 초기화 여부 확인
- PersistenceUnitUtil.isLoaded(Object entity)
- 프록시 클래스 확인 방법
- entity.getClass().getName() 출력
- 프록시 강제 초기화
- Hibernate.initialize(entity)
- 참고 : JPA 표준은 강제 초기화 없음
- 강제 호출 : member.getName()
즉시 로딩과 지연 로딩
Member를 조회할 때 Team도 함께 조회해야 할까?
지연 로딩 LAZY를 사용해 프록시로 조회
@Entity
public class Member extends BaseEntity{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team; // LAZY일 때 team은 프록시로 가져오게 됨
}
team.getName() → 실제 team을 사용하는 이 시점에 초기화됨
Member와 Team을 자주 함께 사용한다면?
→ 즉시 로딩 EAGER을 사용해 함께 조회
@Entity
public class Member extends BaseEntity{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team; // em.find()로 가져올 때부터 바로 가져옴
프록시와 즉시로딩 주의 (즉시 로딩 사용 X)
- 가급적 지연 로딩만 사용(특히 실무에서)
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
- @ManyToOne, @OneToOne은 기본이 즉시 로딩 → LAZY로 설정
- @OneToMany, @ManyToMany는 기본이 지연 로딩
지연 로딩 활용
- Member와 Team은 자주 함께 사용 → 즉시 로딩
- Member와 Order는 가끔 사용 → 지연 로딩
- Order와 Produect는 자주 함께 사용 → 즉시 로딩
지연 로딩 활용 - 실무
- 모든 연관관계에 지연 로딩 사용
- 실무에서 즉시 로딩 사용 X
- JPQL fetch join이나, 엔티티 그래프 기능 사용
- 즉시 로딩은 상상하지 못한 쿼리가 나간다.
영속성 전이(CASCADE)와 고아 객체
영속성 전이: CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
예) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
Parent
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
}
Parent persist할 때 cascade 선언한 밑에 있는 애들 persist 날려줌
주의!
- 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함만 제공
- 다른 것과 연관관계가 있을 때 쓰면 안 됨 → 엔티티의 소유자가 하나일 때만 사용(단일 엔티티에 종속적일 때만)
CASCADE 종류
- ALL : 모두 적용
- PERSIST : 영속
- REMOVE : 삭제
고아 객체
- 고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
- orphanRemoval = true
Parent
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
// 자식 엔티티를 컬렉션에서 제거
- DELETE FROM CHILD WHERE ID = ?
주의!
- 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
- 참조하는 곳이 하나일 때 사용해야 함!
- 특정 엔티티가 개인 소유일 때 사용
- @OneToOne, @OneToMany 만 가능
- 참고 : 개념적으로 부모를 제거하면 자식은 고아가 됨 → CascadeType.REMOVE처럼 동작
영속성 전이 + 고아 객체, 생명주기
- CascadeType.ALL + orphanRemovel = true
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
- 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음
- 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용
실전 예제 5 - 연관관계 관리
글로벌 패치 전략 설정
- 모든 연관관계를 지연 로딩으로
- @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 지연 로딩으로 변경
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "MEMBER_ID")
private Member member;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
영속성 전이 설정
- Order → Delivery를 영속성 전이 ALL 설정
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
- Order → OrderItem을 영속성 전이 ALL 설정
@OneToMany(mappedBy ="order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
참고 강의 :
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
'JPA > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 9. 값 타입(2) (0) | 2023.01.01 |
---|---|
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 9. 값 타입(1) (0) | 2023.01.01 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 7. 고급 매핑 (1) | 2022.12.30 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 6. 다양한 연관관계 매핑 (1) | 2022.12.29 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 5. 연관관계 매핑 기초(2) (0) | 2022.12.29 |