김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 정리
1. 프록시
- Member에 프로퍼티로 Team을 가지고 있을 때 em.find로 member을 조회하면 쿼리로 member와 team을 같이 조회한다.
- team은 사용하지 않을 것이고 member의 username만 사용한다면 굳이 team을 조회할 필요가 없다.
- 필요할 때 조회하기 위해 프록시를 사용한다.
2. 프록시 기초
- em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference(): 데이터 조회를 미루는 가짜(프록시) 엔티티 객체 조회(DB로 쿼리를 날리지 않고 파라미터로 넣어준 클래스를 상속받은 가짜(프록시) 엔티티를 만들어서 프록시 객체가 영속성 컨텍스트에 들어간다.)
3. 프록시 특징
- 실제 클래스를 상속 받아서 만들어짐
- 실제 클래스와 겉모양이 같다.
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
- 프록시 객체는 실제 객체의 참조(target)를 보관
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크 시 주의해야함(== 비교 실패, 대신 instance of 사용)
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생(하이버네이트는 org.hibernate.LazyInitialzationException 예외를 터트림)
- 프록시 객체의 초기화
(1) 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회
(2) 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청 - 초기화
(3) 영속성 컨텍스트는 DB를 조회해서 실제 엔티티 객체를 생성
(4) 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관
(5) 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환
- 프록시 확인
(1) 프록시 인스턴스의 초기화 여부 확인: PersistenceUnitUtil.isLoaded(Object entity)
(2) 프록시 클래스 확인 방법: entity.getClass().getName() 출력(..javasist.. or HibernateProxy...)
(3) 프록시 강제 초기화: org.hibernate.Hibernate.initialize(entity);
(4) 참고: JPA 표준은 강제 초기화 없음 - 강제 호출: member.getName()
4. 영속성 전이: CASCADE
- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
예: 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
- 코드
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent",cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
}
- 영속성 전이: 저장
- 영속성 전이: CASCADE - 주의!
(1) 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
(2) 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
- CASCADE의 종류
(1) ALL: 모두 적용
(2) PERSIST: 영속
(3) REMOVE: 삭제
5. 고아 객체 제거
- 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent",cascade = CascadeType.PERSIST,orphanRemoval = true)
private List<Child> children = new ArrayList<>();
}
- 매핑 어노테이션의 속성으로 orphanRemoval = true
- 만약 find로 Parent를 조회해서 getChildren으로 리스트를 가져온다음 remove로 0번째 인덱스를 지웠다고 해보자. 원래는 연관관계의 주인이 아니기 때문에 수정쿼리가 날라가지 않는데 orphanRemoval 속성에 의해 db에 반영되도록 Child테이블에 delete from 쿼리가 나간다.
- 고아객체 - 주의
(1) 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
(2) 참조하는 곳이 하나일 때 사용해야함!
(3) 특정 엔티티가 개인 소유할 때 사용
(4) @OneToOne, @OneToMany만 가능
(5) 참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.
6. 영속성 전이 + 고아 객체, 생명주기
- CascadeType.ALL + orphanRemoval=true
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
- 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음
- 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용
'개발 > Java&Kotlin' 카테고리의 다른 글
[Spring] 데이터 액세스 층의 설계와 구현 (0) | 2022.07.20 |
---|---|
[Java] Map안에 Map 안에 List 만들기 (0) | 2022.07.19 |
[JPA] 고급 매핑 (0) | 2022.07.18 |
[Java] 트리 (0) | 2022.07.14 |
[JPA] 다양한 연관관계 매핑 (0) | 2022.07.14 |