-
FetchType.EAGER, FetchType.LAZYSpring(Web) 2024. 3. 20. 22:51
기존에 알고있듯이 @OneToMany와 같은 어노테이션이 붙어있는 필드 엔티티는 FetchType.LAZY가 기본값이고, @ManyToOne과 같은 어노테이션은 FetchType.EAGER가 기본값이다.
FetchType.EAGER가 붙어있는 필드 엔티티는, 해당 필드를 포함한 엔티티를 조회하는 순간 해당 필드 엔티티도 조회하게 된다.
@xxToOne 연관관계인 필드 엔티티에는 FetchType.LAZY를 붙이는게 대부분 좋다는 것을 김영한님 강의에서도 들어서 알고는 있었는데, 붙이지 않고 구현한 경우가 생겨 트러블 슈팅을 하게 되었다.
지금 개발중인 비즈니스 로직에는 ChatRoom(채팅방 정보), UserChatRoom(채팅방에 참여하고있는 사용자 정보), Chat(채팅 메세지 정보) 총 세개의 엔티티가 있다. ChatRoom - UserChatRoom이 1:N, ChatRoom - Chat도 1:N로 ChatRoom에 물려있는 형태로 구현을 하게 되었다. 결국 모든 엔티티에는 Member가 묶여있게 되는데, 그래서 FetchType.EAGER가 기본값인 @xxToOne 연관관계를 가진 엔티티들을 조회할 때 마다 member를 아래 로그와 같이 100개 가량의 파라미터를 이용해 조회하는 것을 발견하게 되었다.
UserChatRoom.java (수정 전)
@Entity @Getter @Builder @AllArgsConstructor @NoArgsConstructor public class UserChatRoom extends BaseEntity { @Id @GeneratedValue private long userChatRoomId; @JoinColumn(name = "chat_room_id") @ManyToOne private ChatRoom chatRoom; @JoinColumn(name = "member_id") @ManyToOne private Member member; LocalDateTime lastAccessTime; @Override public String toString() { return "UserChatRoom{" + "userChatRoomId=" + userChatRoomId + ", chatRoom=" + chatRoom.getTitle() + ", member=" + member + ", lastAccessTime=" + lastAccessTime + '}'; } }
수정 전 SQL log
22:35:10.925 [http-nio-8080-exec-2] TRACE org.hibernate.orm.jdbc.bind -- binding parameter (1:BIGINT) <- [1] 22:35:10.927 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL -- select m1_0.member_id, m1_0.address, m1_0.email, m1_0.is_deleted, m1_0.nickname, m1_0.phone_number, m1_0.profile_image, m1_0.provider, m1_0.refresh_token, m1_0.score from member m1_0 where m1_0.member_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) and ( m1_0.is_deleted = false ) Hibernate: select m1_0.member_id, m1_0.address, m1_0.email, m1_0.is_deleted, m1_0.nickname, m1_0.phone_number, m1_0.profile_image, m1_0.provider, m1_0.refresh_token, m1_0.score from member m1_0 where m1_0.member_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) and ( m1_0.is_deleted = false )
수정 후
@JoinColumn(name = "chat_room_id") @ManyToOne(fetch = FetchType.LAZY) private ChatRoom chatRoom; @JoinColumn(name = "member_id") @ManyToOne(fetch = FetchType.LAZY) private Member member;
수정 후 SQL log
22:42:30.461 [http-nio-8080-exec-2] TRACE org.hibernate.orm.jdbc.bind -- binding parameter (1:VARCHAR) <- [02236e5e-eb9e-43b0-9eb3-b54a7f05bc5e] 22:42:30.462 [http-nio-8080-exec-2] DEBUG org.hibernate.SQL -- select m1_0.member_id, m1_0.address, m1_0.email, m1_0.is_deleted, m1_0.nickname, m1_0.phone_number, m1_0.profile_image, m1_0.provider, m1_0.refresh_token, m1_0.score from member m1_0 where m1_0.member_id=? and ( m1_0.is_deleted = false ) Hibernate: select m1_0.member_id, m1_0.address, m1_0.email, m1_0.is_deleted, m1_0.nickname, m1_0.phone_number, m1_0.profile_image, m1_0.provider, m1_0.refresh_token, m1_0.score from member m1_0 where m1_0.member_id=? and ( m1_0.is_deleted = false )
결국 대부분의 경우에는, FetchType.LAZY로 구현하는 것이 부하를 줄이고 불필요한 쿼리를 날리지 않게 될 것이다.
하지만 특정한 경우에는 해당 엔티티에 포함된 다른 엔티티들의 정보를 한 번에 불러와 사용하는것이 성능상/비즈니스 로직 상으로 이득이 될 때가 있다. 잘 생각해서 적용해야 할듯.
'Spring(Web)' 카테고리의 다른 글
검색 조건이 담긴 Body를 쓰는 HTTP 요청 METHOD는 GET일까 POST일까? (0) 2024.04.27 Pairing 기술블로그 (0) 2024.04.08 Spring Controller의 Stateless (0) 2022.07.07 Spring 핵심 원리 3 - 싱글톤 패턴과 스프링 컨테이너 (0) 2022.06.24 Spring 핵심 원리 2 - 회원 도메인 생성, 테스트 (0) 2022.05.24