-
JPA (7) - 프록시,고아객체,전이카테고리 없음 2024. 1. 11. 16:25
1. 프록시
> 다대일 관계에서 Member를 조회할 때 꼭 Team이 필요한가?
> 필요한 객체만 조회하고 싶음! (그냥 조회하면 join해야해서 구리다)
//회원과 팀 모두 조회 public void printUserAndTeam(String memberId) { Member member = em.find(Member.class, memberId); Team team = member.getTeam(); } //회원만 조회 public void printUser(String memberId) { Member member = em.find(Member.class, memberId); Team team = member.getTeam(); }
1.2 em.find() vs em.getReference()
> em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
> em.getReference() : 데이터베이스 조회를 미루는 가짜 엔티티 객체 조회
(일단 쿼리는 안나가고 객체 조회 -> 가짜 프록시 객체 만들고, 실제 조회나 기타 member에 속성을 불러오면 그때 호출)
> 프록시의 특징
- 실제 클래스 상속 받아서 만들어짐 (완벽한 위임 객체)
- 프록시는 실제 객체 상속받고 + 안에 메서드를 위임할 실제 객체를 담고 있음
-> 사용자가 프록시를 통해서 특정 로직을 호출하면, 실제 객체를 호출해서, 로직을 수행함
1.3 프록시 객체 초기화
Member member = em.getReference(Member.class,1L); member.getName();
- getReference하는 순간에는 껍데기만 있음
(식별자 정도만 가짐 -> 초기에 설정해준 값은 그냥 꺼낼 수 있음 getId는 DB조회 안함 )
- getName()이 호출되면, 영속성 컨텍스트에서 프록시 초기화 요청 (em.find처럼) -> 실제 엔티티가 생성됨
-> target이 초기화되면, target.getName()호출
*DB에 바로 접근하는게 아니라 영속성 컨텍스트에 요청함 따라서 1차 캐쉬에 찾으려는 값이 있으면 이를 돌려줌
1.4 프록시의 특징
> 한번만 초기화
> 초기화시 프록시가 실제 엔티티로 바뀌는 것 아님!! -> 초기화 후 실제 엔티티 접근 가능
> 프록시는 원본을 상속받음 따라서 타입 체크시 주의 (==동등성 비교 실패, instanceof사용하자)
> 1차 캐쉬에 엔티티가 이미 있으면 em.getReference()호출해도 실제 엔티티 나옴
> 영속성 컨텍스트에 도움을 받을 수 없는 준영속 상태에서 프록시 초기화 시도하면 문제 발생 (프록시가 준영속)
* JPA는 인스턴스의 늦은 비교에 대해 같은 영속성 컨텍스트, 같은 트랜잭션이면 항상 같다. 이를 보장해주어야함
em.find던, ref던 영속성 컨텍스트를 지나가기 때문에 여기에 값이 있으면, 이걸 돌려준다.
따라서 영속성에 있을때 em.getReference해도 초기화가 아닌, 실제 엔티티 리턴
반대로 em.getReference를 통해 영속성 컨텍스트를 한번 초기화한 뒤에 em.find로 같은 엔티티 조회하면, 프록시로 나온다.
1.5 프록시 확인
> 프록시 인스턴스의 초기화 여부 확인
emf.getPersistenceUnitUtil.isLoaded(entity)
> 프록시 클래스 확인
entity.getClass().getName() //뒤에 Procy붙음
> 프록시 강제 초기화
org.hibernate.Hibernate.initialize(entity) // JPA 표준에는 강제 초기화 없다 걍 강제로 아무거나 호출해라 (member.getName())
2. 즉시 로딩과 지연 로딩
@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; }
> 지연 로딩으로 LAZY 조회시 연관 엔티티를 프록시로 가져온다!
> 실제로 member에서 team을 사용하는 시점 (member.getTeam().getName())에서 초기화 (DB조회)
> member에서 항상 team을 조회해야한다면, 즉시 조회를 고려하고싶겠지만 하지마라!!! (패치 조인으로 해결가능)
* 가급적 지연 로딩만 사용하자 (즉시 로딩은 예상하지 못한 SQL이 발생한다. JPQL에서 N+1문제를 발생시킨다. )
* xxToOne들은 기본전략이 즉시 로딩이다 LAZY로 바꾸자
* N+1문제란 연관관계 설정된 엔티티 조회시, 조회된 엔티티만큼 연관관계 조회 쿼리가 나가는 현상
(조인해서 한번에 읽어오는게 아니라 그냥 select 무식하게 계속 나감)
> N+1문제 추후에 다시 정리
4. 영속성 전이 (CASCADE)
> 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
(부모 저장시 자식도 저장)
Parent parent = new Parent(); Child child1 = new Child(); Child child2 = new Child(); parent.addChild(child1); parent.addChild(child2); //원래는 3번 호출해야함 만약 parent만 호출하면 parent는 DB에 없는 Child가지는 것 em.persist(parent); em.persist(child1); em.persist(child2); 현재상태 일대다 관계
//전이속성 적용 주인말고 반대에 적용 @OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST) @Entity public class Parent{ //.. @OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST) List<Child> childList = new ArrayList<>(); }
> 영속성 전이는 연관관계 매핑과는 관계 없다! -> 영속화할때 연관 엔티티도 함께 영속화하는 편리함 제공
4.1 CASCADE 종류
> ALL:모두적용, PERSIST: 영속, REMOVE:삭제, MERGE:병합,REFRESH,DETACH
> 하나가 여러개를 모두 관리하, 전이 속성을 사용하자 (소유자가 하나일 경우에만 사용!)
> 단일 엔티티에 모두 종속적인 다의 엔티티에 적용 (게시판과 첨부파일)
5. 고아 객체
> 고아 객체 제거: 부모 엔티티와 연관관계 끊어진 자식 엔티티 자동 삭제
> orphanRemoval = true속성 사용
Parent parent1 = em.find(Parent.class, id); parent1getChildren().remove(0); //DELETE 쿼리 자동으로 날아감
> 전이와 살짝 다름, 전이는 부모가 완전 삭제되면, 같이 사라지는 것 -> 고아객체는 부모에서 자식만 똑 제거
> 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 생각하고 삭제 기능임
> 참조하는 곳이 하나일 때 사용해야한다!! (부모-자식, 부모,삼촌 <-> 자식 관계에서 부모가 자식 삭제시 삼촌에서도 의도하지않게 삭제됨)
> 특정 엔티티가 개인 소유일 때 사용 (OneToOne, OneToMany에서만 사용 가능)
* OrphanRemoval이 true일때 부모 삭제되면 자식도 삭제됨 (CascadeType.REMOVE처럼동작)
5.1 영속성 전이 + 고아 객체
> 스스로 생명주기를 관리하는 엔티티는 em.persist(), em.remove()로 제거
> 두옵션을 모두 활성화하면, 부모 엔티티로 자식의 모든 생명주기 관리 가능하다.