
Book 과 Author 를 일대일 매핑하는 경우
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "book_id")
private Long id;
@Column(name = "book_name")
private String bookName;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
}
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "author")
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "author_id")
private Long id;
@Column(name = "author_name")
private String name;
}
@Service
@RequiredArgsConstructor
@Transactional
public class BookService {
private final BookRepository bookRepository;
public BookResponse getAuthorName(long bookId) {
Book book = bookRepository.findById(bookId).orElseThrow();
return new BookResponse("홍길동");
}
}
Query
Hibernate:
select
b1_0.book_id,
b1_0.author_id,
b1_0.book_name
from
book b1_0
where
b1_0.book_id=?
getAuthor() 등의 Author 객체를 참조하기 전까지 Author 객체를 로드하지 않는다.
Book 과 Author 를 일대일 양방향 매핑하는 경우
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "author")
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "author_id")
private Long id;
@OneToOne(mappedBy = "author", fetch = FetchType.LAZY)
private Book book;
@Column(name = "author_name")
private String name;
}
Hibernate:
select
b1_0.book_id,
b1_0.author_id,
b1_0.book_name
from
book b1_0
where
b1_0.book_id=?
Book -> Author 를 조회하는 경우(주인이 조회하는 경우) 에는 Lazy 로딩이 된다.
@Service
@RequiredArgsConstructor
@Transactional
public class AuthorService {
private final AuthorRepository authorRepository;
public AuthorResponse getBookName(long authorId) {
Author author = authorRepository.findById(authorId).orElseThrow();
return new AuthorResponse("책이름");
}
}
Hibernate:
select
a1_0.author_id,
a1_0.author_name
from
author a1_0
where
a1_0.author_id=?
Hibernate:
select
b1_0.book_id,
b1_0.author_id,
b1_0.book_name
from
book b1_0
where
b1_0.author_id=?
하지만 Book 객체를 참조하지 않았는데 Query 가 2번 실행되어 Eager 패치 전략이 사용된다.
위 예시들을 통해 정리해보면 연관관계의 주인이 호출할 때는 지연 로딩이 정상적으로 동작하지만, 연관관계의 주인이 아닌 곳에서 호출할 때는 즉시 로딩으로 동작한다는 것을 알 수 있다.
지연 로딩으로 설정이 되어있는 엔티티를 조회할 때는 프록시로 감싸서 동작하게 된다. 프록시는 null을 감쌀 수 없기 때문에 이와 같은 문제점이 발생한다.
테이블을 생각해보면 Book 테이블에는 Author 테이블의 pk를 fk로 사용하기 때문에 fk or null 임을 알 수 있다.
하지만 Author 테이블에는 Book 테이블과 연관되어 있다는 정보가 없기 때문에 이를 확인하기 위해 쿼리를 날리고 이 때문에 FetchType.LAZY가 무시된다.
일대다인 경우 항상 다쪽이 왜래키를 가진다. 이 경우 일에 해당하는 객체는 연관관계의 주인이 아니며 매핑된 테이블을 조회할 때, 연관관계 여부를 확인할 수 없다.
일(1)에 해당하는 엔티티의 다(N) 에 해당하는 연관 관계 필드는 컬렉션 타입이다. 지연 로딩을 적용할 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 하이버네이트는 컬렉션 래퍼라는 것을 제공한다. 이것이 컬렉션에 대한 프록시 역할을 합니다. 따라서 SELECT 쿼리를 이용하여 존재 여부를 확인할 필요 없이 지연 로딩이 정상적으로 작동하게 된다.
즉, 컬렉션은 값이 없는 것을 empty로 표현할 수 있어 FetchType.LAZY가 정상작동한다.
[JPA] @OneToOne에서 Fetch 전략을 Lazy로 설정했을때 발생하는 이슈
[JPA] @OneToOne 에 지연 로딩을 적용했지만, 왜 지연 로딩이 안되고 즉시 로딩이 되는 걸까요?