프록시
![](https://blog.kakaocdn.net/dn/wJNnp/btsjO2SU2bm/45PJTgctelQsm2o6ZhHHIK/img.png)
- Member를 조회할 때 Team도 함께 조회를 해야할까??
- 비지니스 로직상 Member를 조회할 때 Team도 같이 필요한 경우에는 조회해야할 것이다.
- 반면, Team이 필요한 경우가 드문 경우에는 Team을 같이 조회하는건 비효율적이다.
프록시 기초
![](https://blog.kakaocdn.net/dn/Po3dq/btsjO17yjNR/BnWV0HmKGL50817nj56uHk/img.png)
em.find()
vsem.getReference()
em.find()
: 데이터베이스를 통해서 실제 엔티티 객체를 조회
em.getReference()
: 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체를 조회
프록시 특징
![](https://blog.kakaocdn.net/dn/cYr8q9/btsjO9dAm2r/QAU9C2tjHQjYfXJHxp6Do0/img.png)
- 프록시 클래스는 실제 클래스를 상속 받아서 만들어 진다.
- 따라서, 실제 클래스와 겉 모양이 똑같다.
- 타입 체크시 주의 해야한다!
- ==로 비교가 불가능하고, instance of 로 비교해야한다.
- JPA에서 프록시를 사용하는지 안 하는지 모를테니까, 객체 타입 체크는 반드시 instance of로 한다고 생각하면 된다.
- 프록시 객체는 처음 사용될 때 한 번만 초기화 된다.
- 그렇다고 프록시 객체가 실제 엔티티로 바뀌는게 아니다. 초기화 되면 프록시 객체를 통해서 실제 엔티티에 접근 가능하다.
getId()
나getName()
을 호출하는 순간 실제 엔티티를 조회하는 쿼리를 실행한다.- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
- 영속성 컨텍스타에 찾는 엔티티가 이미 있으면,
em.getReference()
를 호출해도 실제 엔티티가 반환된다.- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다.
프록시 객체의 초기화
![](https://blog.kakaocdn.net/dn/c7ziBR/btsjJThSDBF/oLBULiTYNjdFApcda5kLt0/img.png)
프록시 확인
emf.getpersistenceUnitUtil.isLoaded(Object entity)
: 프록시 인스턴스 초기화 여부 확인
entity.getClass().getName()
: 프록시 클래스 확인
org.hibernate.Hibernate.initialize(entity)
: 프록시 강제 초기화- 다만, JPA 표준에 없다. Hibernate에서만 지원한다.
즉시 로딩과 지연 로딩
지연 로딩 LAZY를 사용해서 프록시로 조회
@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;
}
- Member를 가져올 때(
em.find
)가 아니라, Team을 가져올 떄(member.getTeam()
) 비로소 초기화 된다(DB 조회)
- Member에서 Team을 자주 조회하지 않을 때 사용한다.
즉시 로딩 EAGER를 사용해서 함께 조회
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
- Member를 가져올 때
JOIN
을 사용해서 한꺼번에 쿼리를 날리게 된다.
- Member에서 Team을 자주 조회할 때 사용한다.
프록시와 즉시로딩 주의
- 실무에서는 가급적 지연로딩만 사용하는 것이 좋다.
- 즉시로딩을 사용하면 예상하지 못 한 SQL이 발생한다.
- 특히, JPQL에서 N+1문제를 발생시킨다.
em.find()
는 JPA가 최적화 할 수 있지만, JPQL로 작성할 경우 SQL로 해석된다.
- 따라서, select쿼리를 날린 후, EAGER인 엔티티에 대해 또 select쿼리를 날린다.
- 따라서,
LAZY
로 설정한 후, JPQL작성 시fetch join
을 활용하면 한 꺼번에 가져올 수 있다.
@ManyToOne
,@OneToOne
은 기본이 즉시 로딩이다. 따라서, LAZY로 설정하는 걸 습관화하자!
@OneToMany
,@ManyToMany
는 기본이 지연 로딩이다.
지연 로딩 활용
![](https://blog.kakaocdn.net/dn/pCTTR/btsjPKEgho3/Ho6LyXtl7kZM3adfonWg20/img.png)
- Member와 Team은 자주 함께 사용 → 즉시 로딩
- Member와 Order는 가끔 사용 → 지연 로딩
- Order와 Product는 자주 함께 사용 → 즉시 로딩
![](https://blog.kakaocdn.net/dn/p84J6/btsjJaRPpEM/YqvURAyBFpmPemjHx1oF20/img.png)
![](https://blog.kakaocdn.net/dn/w8GYV/btsjRV55WTY/kxWDAGs10E6KssvPmKbyMk/img.png)
실무에서는?
- 모든 연관관계에 지연 로딩을 사용해야한다!
- 실무에서는 절대 즉시 로딩을 사용하지 마라!
- JPQL fetch join이나 엔티티 그래프 기능을 사용!
- 즉시 로딩은 상상하지 못 한 쿼리가 나간다!
영속성 전이: CASCADE
![](https://blog.kakaocdn.net/dn/WWOL4/btsjOn4cKFY/wqHXLfVYmUQ9G2tDDfvYN0/img.png)
- 특정 엔티티를 역속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
- ex) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChidl(child2);
em.persist(child1);
em.persist(child2);
em.persist(parent);
- CASCADE를 설정하지 않으면, 위 코드 처럼 3가지를 모두 persist해야한다.
- 즉, 영속성이 전이되지 않는다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<>();
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChidl(child2);
em.persist(parent);
- CASCADE를 설정하면, 위 코드 처럼 parent만 persist해도 3 쿼리가 모두 나가게 된다.
- 즉, 영속성이 전이된다.
CASCADE 주의사항
- 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.
- 하나의 부모가 자식의 라이프사이클을 완전히 관리할 때만 사용해야한다.
- ex) 게시글 - 첨부파일의 관계에서 첨부파일이 하나의 게시글에만 관리될 때
- 게시글 - 첨부파일의 관계에서 첨부파일이 채팅이나 다른 게시글에서 사용될 때는 X
CASCADE의 종류
- ALL: 모두 적용
- PERSIST: 영속
- REMOVE: 삭제
- MERGE: 병합
- REFRESH: REFRESH
- DETACH: DETACH
고아 객체
orphanRemoval = true
- 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 옵션이다.
parent.getChildren().remove(0);
- 자식 엔티티를 컬렉션에서 삭제하면, 자동으로 자식 엔티티가 delete된다.
고아 객체 주의사항
- 참조하는 곳이 하나일 경우에만 사용해야한다
- 즉, 특정 엔티티가 완전 개인 소유할 때에만 사용한다.
@OneToOne
,@OneToMany
만 사용 가능하다.
- 참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 즉,
CascadeType.REMOVE
처럼 동작한다.
영속성 전이 + 고아 객체, 생명주기
CascadeType.ALL + orphanRemoval=true
- 스스로 생명주기를 관리하는 엔티티는
em.persit()
로 영속화,em.remove()
로 제거할 수 있다.
- 그런데, 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명주기를 완전히 관리할 수 있다.
- 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용하다.
Uploaded by N2T
(23.06.12 10:36)에 작성된 글 입니다.