만약 비즈니스 로직에서 Member에 대한 정보를 가지고올 때 .find()를 통해 Member Entity와 연관관계 매핑된 Entity를 join하여 한번에 다 가지고온다.
하지만 Member에 대한 정보만 쓰려는 케이스는 불필요한 다른관계의 Entity정보까지 join을 통해 가지고오게 되는데 둘 다 쓰는 케이스에서는 편리하겠지만 Member에대한 정보만 사용하는 케이스에서는 낭비가 될 수 있다.
이러한 불필요한 낭비를 JPA는 프록시와 지연로딩을 사용하여 해결하고 있다.
실제클래스를 상속 받아서 만들어지고 겉 모양이 같다.
EntityManager.find() - 데이터베이스를 통해 실제 엔티티 객체 조회
EntityManager.getReference() - 데이터베이스 조회를 미루는 가짜(프록시)엔티티 객체 조회
프록시 객체는 실제 객체의 참조(target)를 갖고, 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
member객체를 em.getReference()로 처음 가져오게되면 target이 없다.
member.getName()으로 실제 Entity의 값을 요청하는 시점에 프록시 객체는 영속성 컨텍스트로 초기화 요청을 보낸다.
초기화 요청을 받은 영속성 컨텍스트는 DB를 조회하여 실제 Entity객체를 생성하여 프록시 객체에 연결해 줌으로써 프록시 객체는 target을 통해 실제 Entity객체에 접근할 수 있다.
( 프록시 객체가 실제 Entity로 바뀌는것이 아니라 실제Entity를 상속받아 target을 통해 실제 Entity에 접근하는 것이다, 이러한 이유로 만약 타입을 비교할 때 실제 Entity타입과 "==" 비교하게되면 false를 반환한다 - instanceof를 사용하여 비교 )
📌
JPA에서는 트랜젝션 안에서 조회된 같은 객체를 "==" 비교했을 때 true라고 보장해줘야 한다는 원칙이 있다.
메소드 | 설명 |
---|---|
emf.getPersistenceUnitUtil.isLoaded(Object entity) | 프록시 인스턴스의 초기화 여부 확인 |
entity.getClass() | 프록시 클래스 확인 |
Hibernate.initialize(entity) | 프록시 객체 강제 초기화 |
fetch타입을 LAZY로 설정해주면 em.find()를 통해 조회할 때 기존에는 연관관계에 해당하는 Entity들을 join하여 전부 select해 왔다면, LAZY로 설정시 Entity를 조회했을 때 연관관계에 해당하는 Entity를 join하지 않고 프록시 객체를 통해 find한 Entity만을 select하여 가지고온다.
그리고 위에서 설명한 프록시 처럼 fetch를 설정한 Entity에 접근하는 시점에 초기화 되어 개별적으로 연관관계에 있는 Entity를 select하는 쿼리를 DB로 보내어 데이터를 가지고온다.
프록시 객체를 사용하지않고 기존처럼 em.find()하여 로딩하는 시점에 모든 Entity를 join하여 조회한다.
가급적 지연 로딩만 사용하는것을 추천하고 있는데 그 이유는 즉시로딩을 적용하게되면 예상하지 못한 쿼리문이 발생할 수 있고, 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
(@ManyToOne, @OneToOne은 기본값이 즉시 로딩임으로 -> LAZY로 설정. )
📌 N+1 문제?
JPQL은 쿼리문을 그대로 번역해서 바로 DB로 요청하기 때문에 만약 JPQL로 "select m from Member m"을 요청했다면 Member의 전체를 리스트로 받게 되는데 이 경우 Member객체 안의 연관관계 매핑된 Entity가 EAGER로 설정되어 있다면 값을 채워줘야함으로 값을 얻기위해서 또 다시 리스트의 크기만큼 쿼리문이 나가게된다.
이처럼 최초 쿼리로 인해 불필요한 추가쿼리 N개가 발생하는 문제를 N+1문제 라고한다.
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때 사용한다
( 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장 )
단일 엔티티에 종속적일때 즉 하나의 부모객체가 자식객체들을 관리할때 사용하는것을 추천한다.
부모와 자식이 라이프사이클이 같기때문에 운영하기 쉽고 부모가 여러명이 된다면 운영하기 어려워질 것이다.
📌 cascade + orphanRemoval
두개의 설정을 모두 설정하게되면 부모엔티티를 통해서 생명주기를 관리할 수 있다.
(이점은 DAO 또는 repository가 없어도 된다)
parent.addChild(child1); // 부모를 통해 자식 추가
parent.addChild(child2);
....
findParent.getChildList().remove(0); // 부모를 통해 자식 삭제
📚 참고 및 자료 출처 : 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)