JPA 스터디 (8장)
8장 프록시와 연관관계 관리
8.1 프록시
-
엔티티가 실제 사용될때 까지 데이터베이스 조회를 지연 하는 방법을 지연 로딩 이라고 한다.
-
지연 로딩 기능을 사용 하려면 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이때 프록시 객체를 사용 한다.
-
프록시 기초
- EntityManager.getReference() 메소드를 사용 하면 된다.
- 이 메소드 호출 시 JPA는 데이터베이스 조회를 하지 않고 실제 엔티티 객체도 생성 하지 않는다. 대신 데이터베이스 접근을 위임한 프록시 객체를 반환함
-
프록시 초기화 과정
- 프록시 객체에 실제 엔티티 값을 호출하여 실제 데이터를 조회
- 실제 엔티티가 생성되어 있지 않을 시 영속성 컨텍스트에 실제 엔티티 생성 요청
- 영속성 컨텍스트는 데이터베이스 조회 후 실제 엔티티 객체 생성
- 프록시 객체는 생성된 실제 엔티티 객체의 참조를 멤버변수로 보관
-
프록시 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화 된다.
- 프록시 객체 초기화 시 프록시 객체가 실제 엔티티로 바뀌는건 아님( 프록시 객체가 초기화 되면 프록시 객체를 통해 실제 엔티티에 접근할 수 있어짐)
- 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시 주의 해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 존재할 시 em.getReference() 메소드 호출 해도 프록시가 아닌 실제 엔티티를 반환 한다.
- 초기화는 영속성 컨텍스트의 도움을 반드시 받아야 하므로 준영속 상태의 프록시를 초기화 시 문제 발생(하이버네이트의 경우 LazyInitializationException 예외 발생 시킴)
-
프록시와 식별자
- 프록시 객체는 PK 값을 보관하고 있기 때문에 엔티티 접근 방식이 프로퍼티(@Access(AccessType.PROPERTY) )일 경우 식별자 값을 조회하는 메소드를 호출 해도 프록시 초기화가 되지 않음
- 연관관계 설정 시 식별자 값만 이용하므로 프록시를 사용 시 데이터베이스 접근 횟수를 줄일 수 있다.
-
프록시 확인
-
PersistenceUnitUtil.isLoaded(Object entity) 메소드 사용 시 프록시 인스턴스의 초기화 여부를 확인할 수 있다.
boolean isLoad = em.getEntityManagerFactory() .getPersistenceUnitUtil().isLoaded(entity);
-
조회한 엔티티가 진짜 엔티티 인지 프록시 인지 확인 시 클래스명을 직접 출력 해봄
System.out.println("memberProxy = "+ member.getClass().getName()); //결과 : memberProxy = jpabook.domain.Member_$$_javassist_0
-
8.2 즉시 로딩과 지연 로딩
-
즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회 한다.
- 설정 방법 : @ManyToOne(fetch = FetchType.EAGER)
-
지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회 한다.
- 설정 방법 : @ManyToOne(fetch = FetchType.LAZY)
-
즉시 로딩
-
@ManyToOne(fetch = FetchType.EAGER) 로 지정 한다.
@Entity public class Member { @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "TEAM_ID") private Team team; ... }
-
즉시 로딩 시 회원과 팀 두 테이블을 조회해야 하는데, 쿼리를 두번 실행하지는 않고 대부분의 JPA 구현체들은 최적화를 위해 가능하면 조인 쿼리를 사용 함
[즉시 로딩시 사용된 조인 쿼리] SELECT M.MEMBER_ID AS MEMBER_ID, M.TEAM_ID AS TEAM_ID, M.USERNAME AS USERNAME, T.TEAM_ID AS TEAM_ID, T.NAME AS NAME FROM MEMBER M **LEFT OUTER JOIN TEAM** T ON M.TEAM_ID = T.TEAM_ID WHERE M.MEMBER_ID = 'member1';
-
내부 조인(INNER JOIN)이 아닌 외부 조인 (LEFT OUTER JOIN)을 사용 한 것을 유심히 봐야 한다.
-
외부 조인을 사용한 것은 현재 TEAM_ID 는 NULL 값을 허용하고 있기 때문에 팀에 소속되지 않은 회원도 있을 가능성이 있기 때문에 외부 조인이 사용 된 것이다.
-
INNER JOIN이 성능, 최적화에 더 유리 한데 이를 사용하려면?
- 외래 키에 NOT NULL 제약 조건을 설정 후 JPA에 알려주면 된다.
- @JoinColumn(name = "TEAM_ID", nullable = false)
- 혹은 @ManyToOne(fetch = FetchType.EAGER, optional = false)
- JPA는 선택적 관계일 때 외부 조인 이용, 필수관계 시 내부 조인 이용함
-
-
지연 로딩
-
@ManyToOne(fetch = FetchType.LAZY) 로 지정 한다.
@Entity public class Member { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "TEAM_ID") private Team team; ... }
-
8.1장 프록시 내용에서 언급했듯이 지연 로딩은 프록시를 사용하며 사용되는 프록시 객체는 실제 사용될 때 까지 데이터 로딩을 미룬다.
-
(참고) 조회 대상이 영속성 컨텍스트에 이미 있다면 프록시 객체를 사용할 이유가 없다. 따라서 이미 로딩 되어 있으면 실제 엔티티를 사용 한다.
-
8.3 지연 로딩 활용
-
JPA 기본 Fetch 전략
- @ManyToOne, @OneToOne : 즉시 로딩 (FetchType.EAGER)
- @OneToMany, @ManyToMany : 지연 로딩 (FetchType.LAZY)
- 기본 전략은 연관된 엔티티가 하나면 즉시로딩, 컬렉션이면 지연 로딩을 사용 한다.
- 컬렉션의 경우 즉시로딩 시 해당 회원 로딩 시 수만건의 데이터가 함께 로딩될 수 있음
- 추천 하는 방법은 모든 연관관계에 지연 로딩을 사용 하는 것이다?!
- 애플리케이션 개발이 어느정도 완료단계에 왔을 때 실제 사용하는 상황을 보고 꼭 필요한 곳에만 즉시로딩을 사용하도록 최적화
-
컬렉션에 즉시로딩 사용시 주의 점
- 컬렉션을 하나 이상 즉시 로딩하는 걸 권장하지 않음
- 컬렉션의 즉시로딩의 경우 항상 외부 조인을 사용 한다. JPA는 일대다 관계를 즉시 로딩 할 때 항상 외부 조인을 사용
- @ManyToOne, @OneToOne
- optional = false : 내부 조인
- optional = true : 외부 조인
- @OneToMany, @ManyToMany
- optional = false : 외부 조인
- optional = true : 외부 조인
8.4 영속성 전이 : CASCADE
-
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때 영속성 전이 기능을 사용 하면 된다.
-
영속성 전이 : 저장
@Entity public class Parent { @Id @GeneratedValue private Long id; @OneToMany(mappedBy = "parent", **cascade = CascadeType.PERSIST**) private List<Child> children = new ArrayList<>(); ... } @Entity public class Child { @Id @GeneratedValue private Long id; @ManyToOne private Parent parent; ... }
- CascadeType.PERSIST 를 설정 시 부모객체에 자식객체들을 추가 한 뒤 부모객체만 영속화 해도 자식 객체까지 모두 영속화 되어 저장 된다!
- 단지 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공 할뿐이다.
-
영속성 전이 : 삭제
- CascadeType.REMOVE 설정 시 부모 엔티티를 삭제하면 자식 엔티티까지 모두 삭제 된다.
- 단 삭제 순서는 외래 키 제약조건을 고려 해 자식 먼저 삭제 후 부모가 삭제 된다.
8.5 고아 객체
-
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 고아 객체(ORPHJAN) 제거 라 한다.
-
사용법 : orphanRemoval = true , 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거 하게되면 자식 엔티티가 자동으로 삭제 되게끔 하는 고아 객체 제거 설정
Parent parent = em.find(Parent.class, id); parent.getChildren().remove(0); // 0번째 인덱스에 있는 자식 엔티티 컬렉션에서 제거 //플러시 일어난 후 SQL DELETE FROM CHILD WHERE ID=?
-
고아 객체 제거는 참조가 제거된 엔티티는 다른 곳에서 참조하지 않은 고아 객체로 보고 삭제 하는 기능
-
이 기능은 참조하는 곳이 하나일 때만 사용 해야 한다.( 삭제한 엔티티가 다른 곳에서도 참조한다면? 문제가 되므로)
-
orphanRemoval 은 @OneToOne, @OneToMany 에서만 사용할 수 있음
8.6 영속성 전이 + 고아객체, 생명주기
- CascadeType.ALL + orphanRemoval = true 를 동시 이용 시 부모 엔티티를 통해 자식의 생명주기를 관리 할 수 있음.
8.7 정리
- 지연로딩 시 프록시 기술 사용!
- 객체 조회 시 연관된 객체를 즉시 로딩하는 방법이 즉시 로딩 이라 하고 연관된 객체를 지연(연관된 객체를 실제 사용할 때까지 지연 시킴) 하여 로딩 하는 방법을 지연 로딩이라 한다.
- 객체 저장 or 삭제 시 연관된 객체도 함께 처리 하는 것을 영속성 전이 라 한다.
- 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제 하려면 고아 객체 제거 기능을 사용 하면 된다.
Author And Source
이 문제에 관하여(JPA 스터디 (8장)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@soonworld/JPA-Study-8장저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)