티스토리 뷰
프록시란?
- 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하는데 이것을 지연 로딩이라 한다.
- 지연 로딩 기능을 사용하기 위해 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라 한다.
프록시 사용
- 엔티티를 실제 사용하는 시점까지 데이터베이스 조회를 미루고 싶으면 EntityManager.getReference() 메소드를 사용하면 된다.
Member member = em.getReference(Member.class, "Member1");
- PersistenceUnitUtil.isLoaded(Object entity) 메소드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있다.
boolean isLoad = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity);
//또는 boolean isLoad = emf.getPersistenceUnitUtil().isLoaded(entity);
System.out.println("isLoaded = " + isLoad); //초기화 여부 확인
- 조회한 엔티티가 진짜 엔티티인지 프록시로 조회한 것인지 확인하려면 클래스명을 직접 출력해보면 된다.
System.out.println("memberProxy = " + member.getClass().getName());
//결과: memberProxy = jpabook.domain.Member_$$_javassist_0
//클래스 명 뒤에 ..javassist..라 되어 있는데 이것으로 프록시인 것을 확인할 수 있다.
프록시 특징
- 프록시 객체는 실제 객체에 대한 참조(target)를 보관한다. 그리고 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
- 프록시 객체는 member.getName() 처럼 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는데 이것을 프록시 객체의 초기화라 한다.
- 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 준영속성 상태의 프록시를 초기화하면 문제가 발생한다. (org.hibernate.LazyInitializationException 예외 발생)
- 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해서 사용해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.
즉시 로딩
엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
@Entity
public class Member {
...
@ManyToOne(fetch = FetchType.EAGER) //fetch 속성을 EAGER로 지정
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
-- 즉시 로딩 실행 SQL
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'
지연 로딩
연관된 엔티티를 실제 사용할 때 조회한다.
@Entity
public class Member {
...
@ManyToOne(fetch = FetchType.LAZY) //fetch 속성을 LAZY로 지정
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
-- em.find(Member.class, "member1") 호출 시 실행되는 SQL
SELECT * FROM MEMBER
WHERE MEMBER_ID = 'member1'
-- team.getName() 호출 시 실행되는 SQL
SELECT * FROM TEAM
WHERE TEAM_ID = 'team1'
영속성 전이: CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용한다.
CASCADE의 종류
- ALL: 모두 적용
- PERSIST: 영속
- MERGE: 병합
- REMOVE: 삭제
- REFRESH: REFRESH
- DETACH: DETACH
CASCADE 활성화
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<Child>();
...
}
@Emtity
public class Child {
@Id @GeneratedValue
private Long id;
@ManyToOne
private Parent parent;
...
}
CASCADE 저장
부모만 영속화하면 CascadeType.PERSIST로 설정한 자식 엔티티까지 함께 영속화해서 저장한다.
private static void saveWithCascade(EntityManager em){
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
child1.setParent(parent); //연관관계 추가
child2.setParent(parent); //연관관계 추가
parent.getChildren().add(child1);
parent.getChildren().add(child2);
//부모 저장, 연관된 자식들 저장
em.persist(parent);
}
CASCADE 삭제
연관된 자식 엔티티도 함께 삭제된다.
Parent findParent = em.find(Parent.class, 1L);
em.remove(findParent);
고아 객체 제거
- 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제한다.
- 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제한다.
- 참조하는 곳이 하나일 때만 사용한다. (예: @OneToOne, @OneToMany)
- 부모를 제거하면 자식도 같이 제거된다. 이것은 CascadeType.REMOVE를 설정한 것과 같다.
고아 객체 제거 기능 설정 및 사용
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<Child>();
...
}
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
//컬렉션에서 첫번째 자식을 제거하면 orphanRemoval = true에 의해서 데이터베이스의 데이터도 삭제된다.
참고
김영한, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘출판주식회사(2015)
'Framework > Spring Data JPA' 카테고리의 다른 글
우아한테크캠프 Pro 5기 - 도메인 이벤트 (0) | 2022.12.30 |
---|---|
우아한테크캠프 Pro 5기 - Spring Data JPA (0) | 2022.11.24 |
[JPA/Hibernate] 고급 매핑 (0) | 2021.06.28 |
[JPA/Hibernate] 연관관계 매핑 기초 (0) | 2021.05.25 |
[JPA/Hibernate] 엔티티 매핑 (0) | 2021.05.10 |
댓글