엔티티가 실제 사용될때 까지 데이터베이스 조회를 지연 하는 방법을 지연 로딩 이라고 한다.
지연 로딩 기능을 사용 하려면 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이때 프록시 객체를 사용 한다.
프록시 기초
프록시 초기화 과정
프록시 특징
프록시와 식별자
프록시 확인
PersistenceUnitUtil.isLoaded(Object entity) 메소드 사용 시 프록시 인스턴스의 초기화 여부를 확인할 수 있다.
boolean isLoad = em.getEntityManagerFactory()
.getPersistenceUnitUtil().isLoaded(entity);
조회한 엔티티가 진짜 엔티티 인지 프록시 인지 확인 시 클래스명을 직접 출력 해봄
System.out.println("memberProxy = "+ member.getClass().getName());
//결과 : memberProxy = jpabook.domain.Member_$$_javassist_0
즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회 한다.
지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회 한다.
즉시 로딩
@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이 성능, 최적화에 더 유리 한데 이를 사용하려면?
지연 로딩
@ManyToOne(fetch = FetchType.LAZY) 로 지정 한다.
@Entity
public class Member {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
8.1장 프록시 내용에서 언급했듯이 지연 로딩은 프록시를 사용하며 사용되는 프록시 객체는 실제 사용될 때 까지 데이터 로딩을 미룬다.
(참고) 조회 대상이 영속성 컨텍스트에 이미 있다면 프록시 객체를 사용할 이유가 없다. 따라서 이미 로딩 되어 있으면 실제 엔티티를 사용 한다.
JPA 기본 Fetch 전략
컬렉션에 즉시로딩 사용시 주의 점
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때 영속성 전이 기능을 사용 하면 된다.
영속성 전이 : 저장
@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;
...
}
영속성 전이 : 삭제
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 고아 객체(ORPHJAN) 제거 라 한다.
사용법 : orphanRemoval = true , 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거 하게되면 자식 엔티티가 자동으로 삭제 되게끔 하는 고아 객체 제거 설정
Parent parent = em.find(Parent.class, id);
parent.getChildren().remove(0); // 0번째 인덱스에 있는 자식 엔티티 컬렉션에서 제거
//플러시 일어난 후 SQL
DELETE FROM CHILD WHERE ID=?
고아 객체 제거는 참조가 제거된 엔티티는 다른 곳에서 참조하지 않은 고아 객체로 보고 삭제 하는 기능
이 기능은 참조하는 곳이 하나일 때만 사용 해야 한다.( 삭제한 엔티티가 다른 곳에서도 참조한다면? 문제가 되므로)
orphanRemoval 은 @OneToOne, @OneToMany 에서만 사용할 수 있음