이전에 읽으면 좋은 글
- JPA란? https://yeonyeon.tistory.com/178
- Entity 매핑 https://yeonyeon.tistory.com/179
목차
- 프록시
- 영속성 전이
1. Proxy; 프록시
- Entity Manager의 .getReference()를 통해 DB 조회를 미루는 가짜(프록시) 엔티티 객체를 조회
- 실제 클래스를 상속 받아 생성해 겉 모양이 같음
- 사용자는 진짜 객체인지 프록시 객체인지 구분 X
- 실제 객체의 참조(target)를 보관
- 프록시 객체에서 메소드 호출 시, 프록시 객체가 실제 객체의 메소드를 호출
(아래 동작 과정 참고)
proxy 동작 과정
Member member = em.getReference(Member.class, “id1”);
member.getName();
- getReference()를 통해 프록시 객체 생성
- 사용자가 프록시 객체의 getName() 호출
- 프록시 객체가 영속성 컨텍스트에 초기화 요청
- 영속성 컨텍스트가 DB를 조회해 실제 Entity 생성
- 프록시의 Member target에서 getName() 호출
- getName() 결과 반환
proxy의 초기화
- 프록시는 첫 사용 시 단 한번만 초기화
- 초기화 != 실제 Entity 객체로 대체
프록시 객체를 통해 실제 Entity 객체로 접근 가능하게 되는 것이지 주소나 값이 대체되는 것이 아님
proxy와 타입체크
JPA는 한 트랜잭션에서 같은 객체를 가져온 경우, ==의 결과가 항상 true로 나와야 한다.
- 프록시 객체는 원본 Entity를 상속받아 사용
- = Entity와 프록시 객체를 '=='으로 비교 시 false
- 👉 instanceof 사용해야 함
- 영속성 컨텍스트에 찾는 Entity가 이미 존재하는 경우, getReference()를 해도 실제 Entity를 반환
- getReference() 후에 find()로 찾은 객체는 Entity가 아닌 Proxy
proxy와 준영속 상태
Member 안에 Team이라는 객체가 존재한다고 가정한다.
Member에 대한 정보만 필요할 때 Team의 정보까지 전부 조회하는건 불필요한 일이다.
이를 방지하기 위해 지연 로딩(LAZY키워드)를 이용해 Team의 조회를 미뤄둘 수 있다.
(+ 즉시 로딩은 EAGER 키워드)
👉 Team을 프록시로 조회하기 때문!
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
// getter, setter ...
}
🔻 지연 로딩의 디폴트 설정
각 연관 관계별 지연 로딩/즉시 로딩 디폴트 설정은 아래와 같다.
- @ManyToOne, @OneToOne의 디폴트: 즉시 로딩
- @OneToMany, @ManyToMany의 디폴트: 지연 로딩
🔻 실무에서 지연 로딩 사용하기
실무에서는 가급적이면 지연 로딩만 사용하는 것이 좋다.
즉시 로딩을 적용하면 예상치 못한 SQL이 발생할 수 있고 JPQL의 경우 N+1 문제를 일으킬 수 있다.
N+1 문제: 쿼리문 실행 후 Member를 가져온다. Team이 즉시로딩으로 세팅되어 있으면 Team의 select 쿼리문을 하나 더 실행시킨다.
(+) 지연 로딩으로 설정 후 Team의 정보를 가져오려면 patch join이나 Entity Graph 기능을 사용.
2. 영속성 전이
- CASCADE
- 특정 Entity를 영속화 하는 경우 연관 Entity까지 함께 영속화
- != 연관 관계
@OneToMany(mappedBy = "test", cascade = CascadeType.PERSIST)
CASCADE의 종류
- ALL: 모두 적용
- PERSIST: 영속
- REMOVE: 삭제
- MERGE: 병합
- REFRESH: refresh
- DETACH: detach
🔻 영속성 전이 예제 살펴보기
Parent Entity
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
// getter, setter, ...
}
Child Entity
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
// getter, settter, ...
}
위와 같은 관계가 있다고 가정한다.
Parent는 Child를 CascadeType.ALL로 지정해두었다.
만약 Parent의 children 필드에 값을 insert하면, Child Entity와 관련된 테이블에도 insert 된다.
만약 children 필드에 값을 delete하면, Child Entity와 관련된 테이블에도 delete가 진행된다.
🔻 고아 객체와 영속성 전이
고아 객체
- 부모 엔티티와 연관 관계가 끊어진 자식 엔티티
- orphanRemoval = true로 하면 자동으로 고아 객체가 삭제 됨
- @OneToMany, @OneToOne에서만 가능
- CascadeType.REMOVE처럼 작동
고아 객체와 영속성 전이는 로직 작성 시 어떤 Entity까지 영향이 가느냐를 고려하게 되므로 신중히 사용해야 함.
보통 소유자가 단 하나일 때나 DDD의 Aggregate Root에서 사용.
본 게시글은 김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 강의를 구매 후 정리하기 위한 포스팅입니다.
내용을 임의로 추가, 수정, 삭제한 부분이 많으며 정확한 이해를 위해서 강의를 구매하시는 것을 추천 드립니다.
'Develop > Java' 카테고리의 다른 글
[Java] toString() 파헤치기 (2) | 2022.02.15 |
---|---|
[JPA] Table 'DB.hibernate_sequence' doesn't exist 에러 (0) | 2022.01.14 |
[JPA] Entity 매핑 (0) | 2022.01.10 |
[JPA] JPA란? (0) | 2022.01.03 |
[Java] Enum에 대해 (4) | 2021.12.13 |