프록시
Member 테이블에 username과 Team team이 존재한다고 할 때, 회원만 출력하고 싶은 경우에도 select는 team도 조회하게 된다. 이것은 큰 낭비인데 JPA는 이것을 프록시로 처리한다.
프록시 기초
- em.find() : 데이터베이스를 통해서 실제 엔티티 객체를 조회한다.
- em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체를 조회한다.
즉, em.getReference()는 select 구문을 안하고도 값을 저장하고 있다.
프록시 특징
- 실제 클래스를 상속 받아서 만들어진다.
- 실제 클래스와 겉 모양이 같다.
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.(이론상)
프록시 객체는 실제 객체의 참조를 보관한다. 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
Member member = em.getReference(Member.class, "Id1");
member.getName();
- 프록시 객체는 처음 사용할 때 한 번만 초기화 한다.
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능해지는 것 뿐이다.
- 프록시 객체는 원본 엔티티를 상속받는다. 따라서 타입 체크시 == 비교가 아니라 instance of를 사용해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.
- em.getReference()로 반환 된 것은 em.find()로 반환 해도 프록시가 반환 된다.
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다. (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트린다)
프록시 확인
- 프록시 인스턴스의 초기화 여부 확인 : emf.getPersistenceUnitUtil.isLoaded(Object entity)
- 프록시 클래스 확인 방법 : entity.getClass().getName()
- 프록시 강제 초기화 : Hibernate.initialize(entity); -> JPA에는 강제 초괴화 없음, hibernate가 제공한다.
즉시 로딩과 지연 로딩
지연 로딩
@ManyToOne(fetch = FetchType.LAZY) //지연 로딩하기
@JoinColumn(name = "TEAM_ID")
private Team team;
지연 로딩은 연관된 class를 프록시로 가져온다.
Member member = em.find(Member.class, 1L); //지연 로딩으로 세팅
Team team = member.getTeam();
team.getName(); // 실제 team을 사용하는 시점에 초기화(DB 조회) 이때 퀴리 나감
즉시 로딩
만약 Member와 Team을 같이 계속 사용할 경우는 즉시 로딩을 사용한다.
@ManyToOne(fetch = FetchType.EAGER) //즉시 로딩
@JoinColumn(name = "TEAM_ID")
private Team team;
Member 조회와 함께 Team도 항상 조회한다. 즉 JPA 구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회한다.
프록시와 즉시로딩 주의 사항
- 가급적 지연 로딩만 사용(특히 실무에서는 사용하지 마라)
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다.
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다. 처음 쿼리가 하나 나갔는데 그것으로 인해 추가 쿼리 N개가 나간다.
- @ManyToOne, @OneToOne은 기본이 즉시 로딩, 지연 로딩으로 변경하자!
- 지연 로딩일 경우 select 할 때 team 내용이 꽉 차있고 싶으면 select m from Member m join fetch m.team/ join fetch를 사용 한다.
지연 로딩 활용
Member와 Team의 관계는 즉시 로딩, Member와 Order는 지연 로딩, Order와 Product는 즉시 로딩을 사용한다 고 할 때,
member1을 조회 할 경우 Team은 즉시 로딩으로 조회가 된다. 주문 내역 List도 조회 한다고 하면 Order가 LAZY로 조회가 되고 그후 상품은 즉시 로딩으로 조회가 바로 된다.
- 모든 연관관계에 지연 로딩을 사용하자
- 실무에서 즉시 로딩을 사용하지 말자
- JPQL fetch 조인이나, 엔티티 그래프 기능을 사용하자
- 즉시 로딩은 상상하지 못한 쿼리가 나가서 위험하다.
영속성 전이(CASCADE)와 고아 객체
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용한다.
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
//persist를 3번 해줘야함
em.persist(child1);
em.persist(child2);
em.persist(parent);
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)
@OneToMany(mappedBy="parent", cascade=CascadeType.ALL)
//둘 중 하나 사용
하지만 parent에 위의 코드를 설정해 주면 em.persist(parent); 할 경우 child까지 모두 persist 한다.
주의 사항
- 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.
- 만약 child를 다른 객체가 관리를 하고 있다면 사용하면 안된다. (소유자가 하나일때만 사용한다)
고아 객체
고아 객체는 부모 엔티티와 연관관계가 끊어진 자식 엔티티이다.
고아 객체 제거는 고아 객체를 자동으로 삭제 해준다. orphanRemoval = true;
@OneToMany(mappedBy="parent", cascade=CascadeType.ALL, orphanRemoval = true)
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
//자식 엔티티를 컬렉션에서 제거
//연관관계 끊어짐
주의 사항
- 참조하는 곳이 하나일 때 사용해야한다.
- 특정 엔티티가 개인 소유할 때 사용한다.
- @OneToOne, @OneToMany만 가능하다.
개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고 아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.
영속성 전이 + 고아 객체, 생명주기
@OneToMany(mappedBy="parent", cascade=CascadeType.ALL, orphanRemoval = true)
두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다.
도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용하게 사용한다.
강의 출처 : https://www.inflearn.com/course/ORM-JPA-Basic
'Spring JPA 공부 > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
JPA - 값 타입 (0) | 2022.12.29 |
---|---|
실전 예제 5 - 연관관계 관계 (0) | 2022.12.28 |
실전 예제 4 - 상속관계 매핑 (0) | 2022.12.27 |
JPA 고급 매핑 (0) | 2022.12.26 |
실전 예제 3 - 다양한 연관관계 매핑 (0) | 2022.12.26 |