즉시 로딩과 지연 로딩 개념을 이해하기 위해 우선 프록시 개념부터 알아보자.
프록시는 지연 로딩을 하기 위해 나왔는데, 사실 EntityManager는 find 말고도 getReference가 있다. 얘는 실제 db를 조회하는건 아니고, 그 실제 db 객체의 target 위치만 저장하고 실제로 불러올 때 가져오는 거. 이걸 가짜 엔티티 객체를 조회하는 방식으로 만들어서 이 가짜 엔티티가 프록시이다.
클래스를 출력하면 진짜 다르고, getId같은 실제로 불러올 때야 select해서 가져오는걸 볼 수 있다.
이 그림이 중요한데, 프록시 클래스가 만들어질 때 영속성 컨텍스트가 이를 관리하기 위해 일반 클래스도 캐시에 저장했던 것 처럼 프록시 클래스도 캐시에 저장한다. 그래서 실제로 요청할 경우 이 프록시 클래스에서 실제 db에 조회해서 정보 달라고 요청하고 영속성 컨텍스트에 실제 값 넣어서 그걸 사용한다. 그래서 프록시 클래스를 등록 해놓고는 영속성 컨텍스트를 초기화 한 뒤에 그대로 사용하면 에러가 뜬다.
말이 어려운데 코드를 보면서 이해하자.
프록시 클래스 만들어 영속성 컨텍스트에 저장한 뒤에 detach든 clear든 이 프록시 클래스를 날려버리면 세션이 없다고 에러 뜬다. 근데 이럼 프록시 클래스말고 일반 클래스도 에러뜨는거 아니냐 할 텐데 아니다.
그래서 이 프록시 클래스가 영속성 컨텍스트 안에 있는지 없는지 등을 확인해주는 기능들이 있다.
그리고 이제 프록시를 사용할 수도 있다는 것을 알아버렸기 때문에 어떤 클래스인지 확인할 때는 ==를 사용하는게 아닌 instance of를 사용해야 한다.
마지막에 ref로 불러왔는데도 프록시가 아닌데, 이는 jpa가 무조건 클래스 == 프록시 클래스 가 true로 나오게 하기 위해 꼼수를 쓰기 때문이다. 가령 find를 먼저 한 다음 ref를 하면 ref도 그냥 클래스를 불러오고, ref를 먼저 한 뒤에 find를 하면 find로 한 것 조차 프록시 클래스로 불러오고..
그래서 getReference를 실제로 쓸 일은 없고 뒤의 지연 로딩을 이해하기 위해 한 거다.
기본값은 즉시 로딩은 eager임.
즉시 로딩으로 설정하면 처음에 불러올 때 다른 테이블에 연관되어 있는 속성들도 한번에 jion해서 다 같이 가져와 member 객체에 저장한다.
반면 지연 로딩의 경우 일단 member에 있는 것들만 불러오고 거기에 연관되어 있는 다른 table이 필요해질 때 그때 join해서 불러온다.
즉시 로딩도 한번에 불러오면 성능에도 좋고 별 문제 없는데? 라고 할 수 있지만 직접 jpql을 적을 때 문제가 된다. 실제로 복잡한 쿼리를 써야 할 때 종종 jpql을 써야 하는데, EntityManager의 find를 사용하면 jpa에서 최적화를 위해 알아서 조절해주지만 내가 쌩으로 jpql을 작성해서 보내기 때문에 일단 select * 해서 불러오고 여기 안에 다른 테이블이 연관되어 있네?? 하면 그제서야 그 다른 테이블 객체 마다 select * .. where member.teamId = team.id 이런식으로 여러개 하니까 문제다.
연관되어 있는 team마다 여러번 select 하는 걸 볼 수 있다.
이걸 해결하기 위해 fetch도 있다. eager로 설정했을 때도 한번에 불러왔던 것 처럼 그냥 객체 불러올 때 연관되어 있는 애들도 한번에 불러오라는 것. 사실 그냥 eager 쓰는거.
그래서 뒤에서 객체 2개가 너무 연관이 깊어 언제나 함께 사용한다면 eager를 쓰고 이런 극한의 예외가 아니면 무조건 lazy를 사용하네 마네 하는데 그냥 무조건 lazy만 사용해라. 성능 문제도 있지만 eager를 사용하면 상상하지도 못한 쿼리가 나간다. 필요할 때 join으로 불러오는게 기본적으로 db 배울 때 알고있는 내용이기도 하고 하니까..
CASCADE는 이런 연관관계와 관련이 없는건데 그냥 여기서 설명한다. 원래 db에 따로따로 insert 넣어서 저장해야 하는걸 그냥 자동으로 해주는거.
무슨말이냐면 코드를 보자.
parent랑 이 아이들을 관리하는 child 객체를 만들었다고 하자. 실제 db에 넣으려면 각 child마다 직접 여러번 persist해서 insert를 날려줘야 하는 귀찮음이 있다.
cascade를 설정하면 내가 parent안에 있는 child들도 모두 insert 날려주라고 설정할 수 있다.
영속성 매핑과는 아무 관련이 없다!! 그냥 편리성을 위한 것 뿐임.
주의점은 단일 소유자일 때와 라이플사이클이 거의 똑같은 때만 써야 한다. 다른곳에서도 참고하고 있는데 자기 따라 막 추가되고 삭제되고 하면 유지보수가 정말 힘들어진다.
고아 객체라고 이렇게 특정 엔티티만이 개인 엔티티를 완전히 소유하고 있을 때, 즉 이 특정 엔티티만으로 해당 개인 엔티티에 접근 가능할 때 생기는 개념인데, 만약 이 특정 엔티티에서 해당 소유 엔티티로 가는 접근을 삭제해서 그 객체로 아무도 접근 못할 때 자동으로 delete를 날려 삭제하는 거다.
그래서 만약 소유주 엔티티 자체가 삭제되면 자식들도 다 삭제된다. 이것만 봐도 정말 확실하게 자신만 소유주가 아니면 사용하지 않아야 하는 걸 알것. 근데 사실 이건 orphanRemoval과 상관없이 cascade일 때 소유 객체 자체가 사라지면 당연히 다 끊어지는 개념이라 orphanRemoval 설정 안해도 알아서 된다.
Cascade와 orphanRemoval을 같이 쓰는 개념에 대해 생각해 볼 필요가 있는데, 다시 보면 알겠지만 child에 대한 코드 하나 없이 child도 persist해서 저장하는걸 대신 할 수 있고 삭제도 마음대로 할 수 있다. 즉, 이 child에 대한 repository를 따로 설정할 필요가 없다는 것. 나중에 유용하게 쓰이는 듯.
이제 실전 예제 할건데 그냥 @ManyToOne 애들을 LAZY로 바꿔주자
...
cascade를 order에 걸어주자. 실제로도 적용되지만 이 order안의 delivery, orderItem에 cascade를 걸어줌으로써 너네는 내 소유고 라이프 사이클을 동시에 하겠다(내가 생성되면 너네들도 같이 생성되어야 한다)라는걸 적용 및 명시해주는 효과도 있다.
'CS > 김영한 스프링 강의' 카테고리의 다른 글
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 10. 객체지향 쿼리 언어1 - 기본 문법 (JPQL) (0) | 2023.09.24 |
---|---|
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 9. 값 타입 (0) | 2023.09.23 |
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 7. 고급 매핑 (0) | 2023.09.22 |
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 6. 다양한 연관관계 매핑 (0) | 2023.09.21 |
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 섹션 5. 연관관계 매핑 기초 (0) | 2023.09.18 |