프록시
왜?
(Member : Team = N : 1)Member를 조회할때 Team도 함께 조회해야 할까?
비즈니스 로직에 따라다르다.
- 거의 항상 Member를 불러낸후, team의 정보와 같이 출력하는 비즈니스 로직있다면,
Team정보까지 쿼리 한번에 가져오는 것이 좋다 - Member만 쓰고, Team을 가끔 쓰는경우,
Member만 쿼리로 호출하는것이 좋음 - JPA에서는 즉시 로딩과 지연 로딩을 활용하여, 문제해결
프록시 기초
- em.find() vs em.getReference()
- find()는 진짜 엔티티 객체를 반환함
- getReference()는 가짜 엔티티 객체를 반환함. => Proxy로 싸여져있고, 들어가있는 구성만 동일(값은없음)
- em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
프록시 특징
프록시 객체의 초기화
- MemberProxy에 Member target에 값이없다면, 초기화를 요청함
- 즉, 프록시에 값을 실제 사용할때, 그때 프록시 값이 없을시, 영속성 컨텍스트로 DB조회가 일어남
프록시의 특징(!!!)
프록시객체는 처음 사용할때 한번만 초기화
프록시객체를 초기화할 때,프록시객체가 실제엔티티로 바뀌는것은 아님,
초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함
(== 비교 실패, 대신 instance of 사용)- member1, member2 의 타입비교시,
member1.getClass() ==member2.getClass()
하면안됨!(member1 instanceof Member)
사용해야함로 해야함( 비교함수를 만들때도, 타입체크시 instanceof로 사용하자)
영속성 컨텍스트에 찾는 엔티티가 이미 있으면
em.getReference()를 호출해도 실제 엔티티 반환JPA에서는 한 트랜잭션 안에서 같은 것을 호출한 모든 것은 같음(
==
) 통해야함(원칙)Member reMember = em. getReference (Member. class, member1. get Id()); System.out.printin("refMember = " + refMember.getClass ()) ; //Proxy Member findMember = em. find (Member.class, member1. getId()); System.out.println("findMember = " + findMember.getClass ()) ; //Member System.out •println("refMember == findMember: " + (refMember == findMember)) ; <!--0-->
지연 로딩
즉시 로딩 EAGER를 사용해서 함께 조회
- Join으로 한번에 다 가져옴
1 | (fetch = FetchType.EAGER) |
프록시와 즉시로딩 주의(!!!)
- 가급적 지연 로딩만 사용(특히 실무에서)
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다
(JPQL로 Member조회시 Member의 row수(N)만큼 Team select 쿼리갯수 더나감) - @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정
- @OneToMany, @ManyToMany는 기본이 지연 로딩
지연 로딩 활용
- 모든 연관관계에 지연 로딩을 사용해라!
- 실무에서 즉시 로딩을 사용하지 마라!
- JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라! (뒤에서 설명)
- 즉시 로딩은 상상하지 못한 쿼리가 나간다.
영속성 전이: CASCADE
참조
기본 특징
- 특정 엔티티를 영속 상태로 만들 떄 연관된 엔티티도 함께 영속상태로 만들고싶을떄
- 예: 부모 엔티티를 저장할 떄 자식 엔티티도 함께 저장, 자동으로 다같이 PERSIST로 영속상태로 만들고싶을때!
- 연관관계, 즉시로딩, 지연로딩과 상관없음!
영속성 전이: CASCADE - 주의
- 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.
CASCADE의 종류
- ALL : 모두 적용 (모든 라이프사이클 같다)
- PERSIST: 영속
- 저장할 때만 씀!
- 단일 소유자일때만 사용해야한다.
- 단일 엔티티에 완전 종속적
- REMOVE: 삭제
- MERGE: 병합
- REFRESH : 리프레쉬
- DETACH: DETACH
고아 객체
- 고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
- orphanRemoval = true
1 | Parent parent1 = em.find(Parent.class, id) |
고아 객체 - 주의
- 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
- 참조하는 곳이 하나일 떄 사용해야함
- 특정 엔티티가 개인 소유할 때 사용
- @OneToOne @OneToMany 만 가능
- 참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화하면, 부모를 제거할 때 자식도 함께제거된다 CascadeType.REMOVE처럼 동작한다.
- REMOVE는 참조가 지워져야한다.
- orphanremovel는 하나만 참조가 지워져도 해당 것만 지워진다.
영속성 전이 + 고아 객체, 생명주기
- CascadeType.ALL + orphanRemovel = true
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
- 두 옵션을 모두 활성화 하면, 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음
- 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용