프록시
멤버와 팀이 연관된 정보라고 했을 때, 어떤 경우에는 멤버만 가져오고 싶고, 어떤 경우에는 멤버와 팀을 다 가져오고 싶을 수 있다.
프록시 기초
- em.find() vs em.getReference()
- em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
- getReference()를 호출하는 시점에는 데이터베이스의 쿼리를 안하고, 실제 사용되는 시점에 쿼리를 날린다.
- getId로 사용할 때는 데이터베이스에서 가져오는 것이 아니라서 쿼리를 날리지 않는다.
- getReference()를 호출하는 시점에는 데이터베이스의 쿼리를 안하고, 실제 사용되는 시점에 쿼리를 날린다.
- 프록시는 실제 클래스를 상속받아서 만들어지고, 실제 클래스와 겉 모양이 같다.
- 프록시 객체는 실제 객체의 참조(target)을 보관하고, 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메서드를 호출한다.
- 유저가 프록시에 getName()을 호출하면 프록시는 영속성 컨텍스트에 초기화를 요청하고, 영속성 컨텍스트는 DB를 조회하고 실제 Entity를 생성한다. 그리고, 프록시는 target.getName()을 호출하여 유저의 요청에 응답할 수 있게 된다.
- 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
- 프록시 객체를 초기화할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능하다.
- 프록시 객체는 원본 엔티티를 상속받는다. 그래서 타입 체크시에 주의해야 한다. ( ==은 실패하고, instance of를 사용해야 한다)
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReferenct()를 호출해도 실제 엔티티를 반환한다.
- 1차 캐시에 이미 있으니까.
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다.
- 하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트린다.
- 확인
- 프록시 인스턴스의 초기화 여부 확인
- emf.PersistenceUtil.isLoaded(Object entity)
- 프록시 클래스 확인 방법
- entity.getClass().getName() 출력
- 프록시 강제 초기화
- org.hibernate.Hibernate.initialize(entity);
- 프록시 인스턴스의 초기화 여부 확인
즉시 로딩과 지연 로딩
- 멤버 클래스에서 Team을 지연 로딩으로 설정해두면, em.find(Member.class, 1L);을 할 때는 Team을 프록시 객체로 두고, 실제 Team을 사용하는 시점에 초기화한다.
- 즉시 로딩은 멤버를 가지고 올 때 팀을 같이 가져오는 것. 시스템에서 멤버와 팀을 90%이상 함께 사용할 때.
- 프록시와 즉시 로딩 주의
- 가급적 지연 로딩만 사용 ( 특히 실무에서)
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
- 즉시 로딩은 JPQL에서 N + 1 문제를 일으킨다.
- JPQL을 Member만 조회하도록 했는데 쿼리는 Team까지 두개가 나간다.
- 최초 쿼리가 1. -> 쿼리 한개만 냈는데 추가 쿼리가 N개가 더 나간다.
- @ManyToOne, @OneToOne (@...ToOne)은 기본이 즉시 로딩 -> LAZY로 설정
- @OneToMany, @ManyToMany (@...ToMany)는 기본이 지연 로딩
- 모든 연관 관계에 지연 로딩 사용
- JPQL fetch 조인이나, 엔티티 그래프 기능을 사용하기
- 즉시 로딩은 상상하지 못한 쿼리가 나간다.
영속성 전이(CASCADE)와 고아 객체
영속성 전이(CASCADE)
- 특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
- 예: 부모 엔티티를 저장할 때, 자식 엔티티도 함께 저장.
- 종류
- ALL: 모두 적용
- PERSIST: 영속
- REMOVE: 삭제
- MERGE: 병합
- REFRESH
- DETACH
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
이런 상태면 persist()를 3번 해야하는데,
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
이렇게 cascade를 해주면 부모만 persist()해줘도 자식이 함께 된다.
그래서 자식이 부모에게만 종속적일 때만 사용해야 한다!
고아 객체
- 고아 객체 제거: 부모 엔티티와 연관 관계가 끊어진 자식 엔티티를 자동으로 제거
- orphanRemoval = true
- 참조하는 곳이 하나일 때 사용해야함. 특정 엔티티가 개인 소유할 때 사용
- @OneToOne, @OneToMany만 가능
- 참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.
CascadeType.ALL + orphanRemoval = True
- 스스로 생명 주기를 관리하는 엔티티는 em.persist()로 영속회, em.remove()로 제거
- 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음.
728x90
'Spring' 카테고리의 다른 글
[JPA 기본편] 객체지향 쿼리 언어1 - 기본 문법 (0) | 2024.06.25 |
---|---|
[JPA 기본편] 값 타입 (0) | 2024.06.24 |
[JPA 기본편] 고급 매핑 (0) | 2024.06.20 |
[JPA 기본편] 다양한 연관 관계 매핑 (0) | 2024.06.19 |
[JPA 기본편] 연관 관계 매핑 기초 (0) | 2024.06.18 |