8장 프록시와 연관관계 정리
프록시
JPA는 지연로딩을 지원한다. 모든 엔티티가 조회될때 사용되어지는것은 아니기 때문에 실제 사용 전 까지는 직접 불러오지 않는것이다. 그렇기에 해당 엔티티를 대신하는 가짜 객체가 필요한데 그것이 프록시 객체이다.
하이버네이트는 지연로딩을 사용하기 위해 프록시 사용하는 방법, 바이트코드 수정하는 방법 두가지를 지원한다. 그 중에 JPA 는 프록시를 사용하는 방법을 택했다.
프록시는 가짜 객체를 뜻하며 실제 클래스를 상속받아 만들어졌기에 겉 모습도 똑같고 안에는 실제 객체에 대한 참조를 보관한다. 이때 프록시의 메소드를 호출하면 실제 객체의 메소드를 호출 한다.
프록시 객체는 처음 사용 시에 한번만 초기화 된다. 초기화 한다는것은 실제 객체로 바뀐다는것이 아니고 실제 객체에 접근할수있도록 참조를 보관한다는 의미이다.
초기화 실행시 실제 엔티티 객체가 없다면 영속성 컨텍스트에 실제 엔티티 생성을 요청하고 생성된다면 객체 참조를 프록시 안에 보관한다.
em.find() : DB를 통해 실제 엔티티 객체를 조회한다.
em.getReference : 가짜 프록시 엔티티 객체 조회 / 만약 이미 영속성 컨텍스트에 실제 엔티티가 생성 되어있다면 실제 엔티티 반환
위 설명 과 같이 영속성 컨텍스트와 접근하여 실체 객체 생성 유무를 판단하기에 준영속 상태 와 같은 영속성 컨텍스트 접근이 어려운 상태에서 초기화를 하는 경우 에는 org.hibernate.LazyInitializationException 같은 예외가 터진다.
member.getName() 처럼 실제 사용될 때, DB 조회해 실제 엔티티 객체를 생성한다. (프록시 초기화)
member.getId() : 프록시 객체는 식별자 값을 가지고 있어서 해당 함수를 호출해도 초기화가 되지 않는다.
즉시로딩 과 지연로딩
즉시로딩 (FetchType.EAGER)
엔티티를 조회하면 연관관계에 있는 엔티티를 함께 조회해서 가져옴
지연로딩(FetchType.LAZY)
엔티티 조회 시, 연관관계에 있는 엔티티들을 실제 사용할때 조회해서 가져옴
즉시로딩
JPA에서는 즉시 로딩 시 주로 JOIN 을 사용한다. 그 중에 inner join이 가장 최적화에 좋다.
하지만 NULL 값이 있다면 inner join 이 불가능 하기에 NOT NULL 옵션을 잘 활용하도록 하자
컬렉션을 가져온다면 즉시로딩은 사용을 자제하자
만약 해야한다면 inner join 말고 outer join을 사용해 혹시라도 null 값으로 인한 오류가 나지 않도록 하
지연 로딩
JPA에 기본전략은 연관관계가 엔티티 하나이면 즉시로딩, 컬렉션이면 지연로딩을 사용한다.
하지만 저자는 모든 연관관계에 지연로딩을 사용하라 말한다.
필자도 프로젝트에서 모두 지연로딩을 사용하였다. 필요시에는 fetch join 이나 batch size 를 사용하였다.
CollectionWrapper
엔티티의 지연로딩을 프록시 객체가 도와준다면, 컬렉션의 지연로딩은 래퍼가 도와준다.
member.getOrders == 컬렉션 초기화 X
member.getOrders.get(0) == 컬렉션 초기화 o
해당 부분을 이해하지 않아서 프로젝트 도중 초기화 되지않은 상태에서 해당 연관 엔티티를 사용하는 로직에서 이슈가 발생함
영속성 전이
특정 엔티티를 영속 상태를 변경하거나 관련된 로직을 수행할때 연관 엔티티도 특정 엔티티의 영속상태를 반영하거나 변경됨에 따라 원하는 방식으로 영속 상태를 지정하고 싶을때 CASCADE 옵션을 사용하면 된다.
즉. 부모 객체에 ~ 하면 자식 객체는 ~ 할것 이라는 맥락으로 이해함
종류 : ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH
CascadeType.ALL: 모든 Cascade를 적용
CascadeType.PERSIST: 엔티티를 영속화할 때, 연관된 엔티티도 함께 유지
CascadeType.MERGE: 엔티티 상태를 병합(Merge)할 때, 연관된 엔티티도 모두 병합
CascadeType.REMOVE: 엔티티를 제거할 때, 연관된 엔티티도 모두 제거
CascadeType.DETACH: 부모 엔티티를 detach() 수행하면, 연관 엔티티도 detach()상태가 되어 변경 사항 반영 X
CascadeType.REFRESH: 상위 엔티티를 새로고침(Refresh)할 때, 연관된 엔티티도 모두 새로고침
이 중에서는 CascadeType.ALL 만 orphanRemoval과 조합해사용을 해봤다. 게시판의 첨부파일에 적용시켜서 게시판이 삭제되거나 수정 될때 이미지 역시 그에 맞는 영속 상태를 따라가게 하기 위해 사용하였다.
고아 객체
JPA는 부모 객체와 연관관계가 끊어진 자식 객체들을 자동으로 삭제해주는 기능을 제공해준다.
자식객체가 부모 객체의 참조가 없어진다면 자동으로 삭제된다.
@OneToOne, @OneToMany 에 사용할수있다. ex. @OneToOne(orphanRemoval = true)
만약 CascadeType.REMOVE 를 같이 사용한다면 부모 객체가 삭제됬을때 자식 객체또한 같이 삭제 된다. 부모가 자식의 생명주기를 모두 담당하기에!
정리
JPA에는 즉시로딩, 지연로딩이 있고 각 상황에 맞게 사용해야 한다. 하지만 왠만하면 지연로딩을 사용하는것이 안정적이다.
프록시는 가짜객체 이며 초기화 시 진짜 객체의 참조값을 지니게 된다.
부모객체에 상태에 따라 자식객체의 영속성 상태를 지정할수있는데 이것을 영속성 전이라고 한다.
Last updated