티스토리 뷰
프록시
프록시란 데이터베이스 조회를 지연할 수 있는 가짜 객체로써 실제 사용하는 시점에서 DB를 조회하도록 도와준다.
실제 객체와는 겉모양이 같고, 실제 객체에 대한 참조를 보관하고있다.
또, 프록시 객체를 호출하면 실제 객체의 메소드를 호출한다.
보통 객체를 조회할 경우 em.fine()를 사용하지만, 프록시 객체를 사용하기 위해선 em.getReference()를 사용한다.
아래 예시를 참고해보자,
DB를 조회하지도않고 실제 객체를 생성하지도않는다.
Member member = new Member(); member.setUsername("아이유"); em.persist(member); em.flush(); em.clear(); // 처음 Id값은 존재하기에 Member findMember = em.getReference(Member.class, member.getId()); // class class hellojpa.Member$HibernateProxy$hlNoHqzk // 하이버네이트 가 만든 가짜 클래스라는 의미 System.out.println("before findMember => " + findMember.getClass()); System.out.println("findMember => " + findMember.getId()); // em.getReference는 아래 값을 찾을 때 진짜 값이 나간다. System.out.println("findMember => " + findMember.getUsername());
그렇다면 프록시의 순서를 알아보자
- 프록시 객체에 member.getName()을 조회할 경우
- 프록시 객체는 실제 엔티티가 생성되어있지 않으면(em.find로 찾은경우, 객체를 생성하고 flush, clear 안한경우) 영속성 컨텍스트에 실제 엔티티 생성을 요청한다. 이것을 초기화라고 한다.
- 영속성 컨텍스트는 DB를 조회하여 실제 엔티티 객체를 생성한다.
- 프록시 객체는 실제 생성된 엔티티객체를 참조한다.
- 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.
프록시의 특징
프록시 객체는 처음 사용할 때 한번만 초기화된다.
- 즉, 영속성 컨텍스트에 데이터가 있는 경우 초기화하지 않는다.
- em.find로 찾았거나, 객체를 생성하고 flush, clear안한경우
- 즉, 영속성 컨텍스트에 데이터가 있는 경우 초기화하지 않는다.
프록시 객체를 초기화 한다고 프록시 객체가 바뀌는것은 아니다. 프록시 객체가 초기화되면 실제 객체를 통해 참조를하여 접근을 한다.
초기화는 영속성 컨텍스트 도움을 받아야한다. 만약 영속성컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화 하면 아래 Exception이 발생한다.
- org.hibernate.LazyInitializationException
- em.close()로 영속성 컨텍스트 닫은경우
- em.detach(reference); // detach로 준영속 만든 경우
- em.clear() //로 영속성 컨텍스트 클리어 한경우
- org.hibernate.LazyInitializationException
프록시객체는 실별자 값을 저장해놓기 때문에 식별자를 조회해도 초기화하지않는다
Team team = em.getReference(Team.class, "team1"); team.getId(); // 초기화 하지 않는다.
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 DB를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다. 아래 참조에서 자세한 내용을 살펴보자
참조) getReference를 하면 프록시 객체가 생성되고 그 후 find를 하면 Proxy객체를 가져올까?
1. getReference, find
Member reference = em.getReference(Member.class, member.getId());
Member m1 = em.find(Member.class, member.getId());
// 둘다 프록시를 가지고온다.
System.out.println(reference.getClass()); // hellojpa.Member$HibernateProxy$hlNoHqzk
System.out.println(m1.getClass()); // hellojpa.Member$HibernateProxy$hlNoHqzk
- 처음 getReference 통해 프록시 객체를 생성하였기에 em.find도 프록시 객체를 생성한다.
2. getReference, getReference 하면 당연히 proxy객체를 가져온다.
3. find, getReference
Member m1 = em.find(Member.class, member.getId());
Member m2 = em.find(Member.class, member.getId());
// 둘다 프록시를 가지고온다.
System.out.println(m1.getClass());
System.out.println(m2.getClass());
프록시 확인방법
- 프록시 초기화 되어있는지 확인방법
boolean isLoad = em.getEntityManagerFactory()
.getPersistenceUnitUtil().isLoaded(entity);
- 프록시가 진짜 엔티티인지 프록시로 조회된 것인지 확인
- 클래스명을 직접 출력하면 된다.
System.out.println(member.getClass().getName());
// hellojpa.Member$HibernateProxy$hlNoHqzk
// 혹은 hellojpa.Member
즉시로딩과 지연로딩
즉시로딩은 엔티티를 조회할 때 연관된 엔티티를 즉시 조회한다. 하이버네이트는 가능하면 SQL조인을 사용해서 한번에 조회한다.
@Entity
public class Member {
// ...
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
em.find(Member.class, "Member1") //를 호출할 때 회원엔티티와 연관된 팀 엔티티도 함께 조회한다.
// 설정방법
@ManyToOne(fetch = FetchType.EAGER)
여기서 주의할점은
em.find시 EAGER로 되어있어 TEAM 테이블을 조회할텐데 이는 left outer join으로 쿼리를 실행한다. Why? Member객체의 TEAM_ID가 null 허용으로 되어있기 때문에 TEAM이 소속안된 Member객체가 있을 수 있기 때문이다. 조회 쿼리를 보면 다음과 같다.
select
member0_.MEMBER_ID as member_i1_3_0_,
member0_.TEAM_ID as team_id3_3_0_,
member0_.USERNAME as username2_3_0_,
team1_.TEAM_ID as team_id1_8_1_,
team1_.name as name2_8_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
그렇기에 Member객체의 TEAM_ID를 아래와 같이 한다면 inner join으로 변경된다.
@Entity
public class Member {
// ...
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID", nullable = false)
private Team team;
}
select
member0_.MEMBER_ID as member_i1_3_0_,
member0_.TEAM_ID as team_id3_3_0_,
member0_.USERNAME as username2_3_0_,
team1_.TEAM_ID as team_id1_8_1_,
team1_.name as name2_8_1_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
지연로딩(LAZY)
연관된 엔티티를 프록시로 조회한다. 즉, 프록시를 실제 사용할 때 초기화 하면서 데이터베이스를 조회한다.
이게 어떤말인지 아래 내용을 보자.
@Entity
public class Member {
// ...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
Member member = em.find(Member.class, "Member1") //를 호출해도 Member Select 쿼리만 나가고
// Team 조회 쿼리는 나가지않는다.
Team team = member.getTeam(); // 프록시 객체
member.getTeam().getName(); //실제 사용하는 JPA가 SQL을 호출해서 팀 엔티티를 조회한다.
JPA 패치전략,
fetch의 기본설정값은 다음과같다.
// fetch 기본값 : EAGER
@ManyToOne
@OneToOne
// fetch 기본값 : LAZY
@OneToMany
@ManyToMany
연관된 엔티티가 하나면 즉시로딩을, 컬렉션이 많으면 지연로딩을 사용한다.
하지만 추천하는 방법은 모두 지연로딩(LAZY)를 사용하는것이다 후에 꼭 필요한 부분에서만 즉시로딩을 사용하면 최적화된다.
또한, 일대다 관계에서 SQL을 날리고싶다면 JPQL fetch이나 엔티티 그래프 기능을 사용해라
즉시로딩을 상상도 못한 쿼리가 나간다.
영속성 전이: CASCADE
- 영속성전이
- CASCADE
- 단일 Entity에 완전히 종속적일 때 사용해도된다.
- 즉, 단일 소유자일 경우 && Parent는 chile와 라이프사이클이 같은 때 사용가능
CASCADE, 고아객체(orphanRemoval)
공통 부모엔티티가 삭제되면 자식엔티티도 삭제된다. 즉, 부모가 자식의 생명주기를 관리한다.
부모엔티티에서 자식엔티티를 삭제할 경우
CASCADE는 자식엔티티가 그대로 남아있는반면 고아객체는 자식엔티티를 제거한다.
parent.getChilds().remove(0);
// 단 부모엔티티에서 자식엔티티를 삭제할 경우 자식엔티티가 지워지는거지
// 자식 엔티티만 삭제하면 지워지지않는다.
em.remove(parent); // 이렇게 자식객체를 직접 remove할 경우에는 Delete Query가 나가지 않는다
'JPA' 카테고리의 다른 글
JPA 사용되는 어노테이션 모음 @Entity, @Id, @Table, @Column - name - nullable - unique - length , @Enumerated - STRING, @Temporal, LocalDate, LocalDateTime, @Lob, jpa의 BigDecimal 만드는법, @Transient 등 (5) | 2023.05.21 |
---|---|
고급매핑 (0) | 2022.02.12 |
다양한 연관관계 매핑 (0) | 2022.02.06 |
양방향 연관관계와 연관관계의 주인2 - 주의점 정리 (0) | 2022.01.27 |
1 - JPA Study - 영속성 관리 - 내부 동작 방식 (0) | 2022.01.16 |
- Total
- Today
- Yesterday
- ngnix
- springboot
- NPM
- 스트림
- container
- webpack
- mvn
- stream
- MAC
- 자바8
- vscode
- 람다
- Vue
- 차이
- Intellij
- docker
- 최종연산
- BeanFactory
- java
- nginx
- 중간연산
- elasticsearch
- AnnotationConfigApplicationContext
- ApplicationContext
- install
- lambda
- JPA
- Vuex
- 영속성 컨텍스트
- map
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |