JPA에서 N + 1 문제를 해결하려고 fetchJoin 을 사용하게 됩니다. 이 때 하나의 entity에서 1:N이 여러개 일때 fetchJoin을 여러번 사용했는데 MultipleBagFetchException이 발생하게 됩니다.
- 즉, 2개 이상의 OneToMany 자식 테이블에 Fetch Join을 선언했을때 발생
- OneToOne, ManyToOne과 같이 단일 관계의 자식 테이블에는 Fetch Join을 써도 됩니다
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Record extends BaseEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "record_seq")
private Long seq;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_seq", foreignKey = @ForeignKey(name="record_member_fk"))
private Member member;
@OneToMany(mappedBy = "record", cascade = CascadeType.ALL, orphanRemoval = true)
private final List<File> fileList = new ArrayList<>();
@OneToMany(mappedBy = "record", cascade = CascadeType.ALL, orphanRemoval = true)
private final List<RecordHashTag> recordHashTagList = new ArrayList<>();
...
}
이러한 상황이 있을 떄 record 에서 file, hashtag들을 가져오려고 할 때 fetchJoin을 쓰지 않게 되면 N + 1이 발생하기 떄문에 fetchJoin 사용
@Override
public List<Record> retrieveRecord(Long memberId, String code, String search, LocalDate date, List<IsPrivate> isPrivateList) {
BooleanBuilder booleanBuilder = searchBuilder(code, search);
return jpaQueryFactory.selectFrom(record).distinct()
.leftJoin(record.fileList, file).fetchJoin()
.leftJoin(record.recordHashTagList, recordHashTag).fetchJoin()
.where(
record.member.seq.eq(memberId).and(booleanBuilder),
eqDate(date),
eqIsPrivateList(isPrivateList)
)
.orderBy(record.seq.desc())
.fetch();
}
해결책
- default_batch_fetch_size
일단 N + 1이 발생하는 이유
- record 가 하나가 있을 때 그 record에 해당하는 file을 찾으려고 합니다.
- 만약 record가 10 개라면? select * from file where record_id = ? 를 10번 합니다.
- 즉, 부모 엔티티의 Key 하나하나를 자식 엔티티 조회로 사용하기 때문인데요
만약 1개씩 사용되는 조건문을 in 절로 묶는다면?
- select * from file where record_id in (1, 2, 3…)
- select * from hashTag where record_id in (1, 2, 3…)
이렇게 묶는 다면 조회수를 엄청 줄일 수 있습니다.
⇒ 이 옵션이 hibernate.default_batch_fetch_size
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 1000
- n절 파라미터로 1,000 개 이상을 주었을때 너무 많은 in절 파라미터로 인해 문제가 발생할수도 있기 때문에 옵션값을 1000이상 주지는 않는다고 합니다.
결과
결론
- N+1 문제를 최대한 in 쿼리로 기본적인 성능을 보장하게 한다.
- OneToOne, ManyToOne 같은 곳에는 fetchJoin을 적용한다.
- OneToMany 같은 관계에서는 데이터가 많은 쪽에 fetchJoin을 사용한다.
- fetchJoin이 없는 자식 엔티티에 관해서는 in 쿼리로 조회하게 된다.
'spring boot > 기술 적용' 카테고리의 다른 글
converter를 만들지 않고 jpa mysql에서 json 사용하기 (0) | 2022.06.28 |
---|---|
fetchJoin과 pagination을 같이 사용할 때 (0) | 2022.06.24 |
@Transactional의 정의, 같은 클래스에서 호출, 예외처리 (0) | 2022.06.22 |
Spring Boot에서 GraphQl 적용해보기 (0) | 2022.05.31 |
JPA - pageable을 이용한 paging (0) | 2021.10.31 |