fetch = FetchType.LAZY
로 설정해 놓았다.survey_document
OneToOne
) Design
OneToOne
) DateManagement
OneToMany
) question_document
OneToMany
) Choice
OneToMany
) wordCloud
fetchJoin, batchSize
밑에 있는 조회 쿼리는 밑의 구성으로 된 정보를 조회할 때 생긴 쿼리이다.
suveydocument #1
조회 쿼리
Hibernate:
select
s1_0.survey_document_id,
s1_0.accept_response,
s1_0.answer_count,
d1_0.date_id,
d1_0.survey_deadline,
d1_0.survey_enable,
d1_0.survey_start_date,
s1_0.survey_description,
d2_0.design_id,
d2_0.back_color,
d2_0.font,
d2_0.font_size,
s1_0.is_deleted,
s1_0.reliability,
s1_0.survey_title,
s1_0.survey_type,
s1_0.user_id
from
survey_document s1_0
left join
date_management d1_0
on s1_0.survey_document_id=d1_0.survey_document_id
left join
design d2_0
on s1_0.survey_document_id=d2_0.survey_document_id
where
s1_0.survey_document_id=?
and (
s1_0.is_deleted = 0
)
2023-10-18T00:32:17.373+09:00 INFO 3496 --- [nio-8082-exec-3] p6spy : #1697556737373 | took 13ms | statement | connection 6| url jdbc:mysql://localhost:3306/surveydb
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d1_0.date_id,d1_0.survey_deadline,d1_0.survey_enable,d1_0.survey_start_date,s1_0.survey_description,d2_0.design_id,d2_0.back_color,d2_0.font,d2_0.font_size,s1_0.is_deleted,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join date_management d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join design d2_0 on s1_0.survey_document_id=d2_0.survey_document_id where s1_0.survey_document_id=? and (s1_0.is_deleted = 0)
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d1_0.date_id,d1_0.survey_deadline,d1_0.survey_enable,d1_0.survey_start_date,s1_0.survey_description,d2_0.design_id,d2_0.back_color,d2_0.font,d2_0.font_size,s1_0.is_deleted,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join date_management d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join design d2_0 on s1_0.survey_document_id=d2_0.survey_document_id where s1_0.survey_document_id=4 and (s1_0.is_deleted = 0);
2023-10-18T00:32:17.395+09:00 INFO 3496 --- [nio-8082-exec-3] p6spy : #1697556737395 | took 0ms | commit | connection 6| url jdbc:mysql://localhost:3306/surveydb
;
Hibernate:
select
q1_0.survey_document_id,
q1_0.question_document_id,
q1_0.is_deleted,
q1_0.question_type,
q1_0.question_title
from
question_document q1_0
where
q1_0.survey_document_id=?
and (
q1_0.is_deleted = 0
)
2023-10-18T00:32:17.405+09:00 INFO 3496 --- [nio-8082-exec-3] p6spy : #1697556737405 | took 1ms | statement | connection 6| url jdbc:mysql://localhost:3306/surveydb
select q1_0.survey_document_id,q1_0.question_document_id,q1_0.is_deleted,q1_0.question_type,q1_0.question_title from question_document q1_0 where q1_0.survey_document_id=? and (q1_0.is_deleted = 0)
select q1_0.survey_document_id,q1_0.question_document_id,q1_0.is_deleted,q1_0.question_type,q1_0.question_title from question_document q1_0 where q1_0.survey_document_id=4 and (q1_0.is_deleted = 0) ;
Hibernate:
select
c1_0.question_id,
c1_0.choice_id,
c1_0.choice_count,
c1_0.is_deleted,
c1_0.choice_title
from
choice c1_0
where
c1_0.question_id=?
and (
c1_0.is_deleted = 0
)
2023-10-18T00:32:17.409+09:00 INFO 3496 --- [nio-8082-exec-3] p6spy : #1697556737409 | took 0ms | statement | connection 6| url jdbc:mysql://localhost:3306/surveydb
select c1_0.question_id,c1_0.choice_id,c1_0.choice_count,c1_0.is_deleted,c1_0.choice_title from choice c1_0 where c1_0.question_id=? and (c1_0.is_deleted = 0)
select c1_0.question_id,c1_0.choice_id,c1_0.choice_count,c1_0.is_deleted,c1_0.choice_title from choice c1_0 where c1_0.question_id=14 and (c1_0.is_deleted = 0) ;
Hibernate:
select
w1_0.question_id,
w1_0.word_cloud_id,
w1_0.word_cloud_count,
w1_0.is_deleted,
w1_0.word_cloud_title
from
word_cloud w1_0
where
w1_0.question_id=?
and (
w1_0.is_deleted = 0
)
2023-10-18T00:32:17.412+09:00 INFO 3496 --- [nio-8082-exec-3] p6spy : #1697556737412 | took 1ms | statement | connection 6| url jdbc:mysql://localhost:3306/surveydb
select w1_0.question_id,w1_0.word_cloud_id,w1_0.word_cloud_count,w1_0.is_deleted,w1_0.word_cloud_title from word_cloud w1_0 where w1_0.question_id=? and (w1_0.is_deleted = 0)
select w1_0.question_id,w1_0.word_cloud_id,w1_0.word_cloud_count,w1_0.is_deleted,w1_0.word_cloud_title from word_cloud w1_0 where w1_0.question_id=14 and (w1_0.is_deleted = 0) ;
Hibernate:
select
c1_0.question_id,
c1_0.choice_id,
c1_0.choice_count,
c1_0.is_deleted,
c1_0.choice_title
from
choice c1_0
where
c1_0.question_id=?
and (
c1_0.is_deleted = 0
)
2023-10-18T00:32:17.414+09:00 INFO 3496 --- [nio-8082-exec-3] p6spy : #1697556737414 | took 0ms | statement | connection 6| url jdbc:mysql://localhost:3306/surveydb
select c1_0.question_id,c1_0.choice_id,c1_0.choice_count,c1_0.is_deleted,c1_0.choice_title from choice c1_0 where c1_0.question_id=? and (c1_0.is_deleted = 0)
select c1_0.question_id,c1_0.choice_id,c1_0.choice_count,c1_0.is_deleted,c1_0.choice_title from choice c1_0 where c1_0.question_id=15 and (c1_0.is_deleted = 0) ;
Hibernate:
select
w1_0.question_id,
w1_0.word_cloud_id,
w1_0.word_cloud_count,
w1_0.is_deleted,
w1_0.word_cloud_title
from
word_cloud w1_0
where
w1_0.question_id=?
and (
w1_0.is_deleted = 0
)
2023-10-18T00:32:17.416+09:00 INFO 3496 --- [nio-8082-exec-3] p6spy : #1697556737416 | took 0ms | statement | connection 6| url jdbc:mysql://localhost:3306/surveydb
select w1_0.question_id,w1_0.word_cloud_id,w1_0.word_cloud_count,w1_0.is_deleted,w1_0.word_cloud_title from word_cloud w1_0 where w1_0.question_id=? and (w1_0.is_deleted = 0)
select w1_0.question_id,w1_0.word_cloud_id,w1_0.word_cloud_count,w1_0.is_deleted,w1_0.word_cloud_title from word_cloud w1_0 where w1_0.question_id=15 and (w1_0.is_deleted = 0) ;
2023-10-18T00:32:17.424+09:00 INFO 3496 --- [nio-8082-exec-3] c.e.s.s.service.SurveyDocumentService : SurveyDetailDto(id=4, title=test2, description=test2, countAnswer=0, questionList=[QuestionDetailDto(id=14, title=test, questionType=1, choiceList=[ChoiceDetailDto(id=15, title=test1, count=0), ChoiceDetailDto(id=16, title=test2, count=0)], wordCloudDtos=[]), QuestionDetailDto(id=15, title=test, questionType=1, choiceList=[ChoiceDetailDto(id=17, title=test3, count=0), ChoiceDetailDto(id=18, title=test4, count=0)], wordCloudDtos=[])], reliability=true, startDate=2023-10-02 00:27:17.1, endDate=2023-10-02 00:27:17.1, enable=true, design=DesignResponseDto(font=font, fontSize=0, backColor=font))
조회 쿼리에서 SurveyDocument
와 @OneToOne
관계인 Design 과 DateMangaement
를 left join
해서 가져 온다.
SurveyDocument
에 연관된 @OneToMany
관계인 QuestionDocument
들을 가져올 때 지연 로딩이 발생하여 다시 쿼리를 날려서 가져온다.
QuestionDocument1
에 @OneToMany
관계로 연관된 Choice
를 가져올 때도 지연 로딩이 발생하여 다시 쿼리를 날려서 가져온다.
QuestionDocument1
에 @OneToMany
관계로 연관된 wordCloud
를 가져올 때도 지연 로딩이 발생하여 다시 쿼리를 날려서 가져온다.
QuestionDocument2
에 @OneToMany
관계로 연관된 Choice
를 가져올 때도 지연 로딩이 발생하여 다시 쿼리를 날려서 가져온다.
QuestionDocument2
에 @OneToMany
관계로 연관된 wordCloud
를 가져올 때도 지연 로딩이 발생하여 다시 쿼리를 날려서 가져온다.
즉, N + 1
문제가 발생하고 있다.
N + 1
문제는 이름에서 알 수 있듯이, 먼저1
의 쿼리가 실행되어 주 엔티티를 조회하고, 그 후에 각각의 하위 엔티티를 조회하기 위해 "N"의 추가 쿼리가 실행되는 패턴을 말합니다. 이 경우N
은 하위 엔티티의 수에 해당하며, 따라서 하위 엔티티의 수가 많을수록 데이터베이스에 부담을 줍니다.)
id
의 surveydocument
에 관한 모든 칼럼을 조회해야 한다.queryDsl
을 이용한 쿼리 작성queryFactory
.selectFrom(surveyDocument)
.join(surveyDocument.questionDocumentList, questionDocument).fetchJoin()
.leftJoin(questionDocument.choiceList, choice).fetchJoin()
.fetchOne();
이때 MultipleBagFetchException
이 발생하였다. (Fetch Join
은 xx**ToOne
은 여러 개 적용이 가능하지만, xx**ToMany
와 같이 1:N
의 관계에서 N
에 대해서는 여러 개 사용할 수 없다.)
application.properties
에 batch size
설정 추가 (Hibernate는 한 번에 지정된 배치 크기만큼의 엔티티를 데이터베이스에서 가져오게 됩니다.)
spring.jpa.properties.hibernate.default_batch_fetch_size=1000
in
절이 사용되며 쿼리 수를 줄였다. (6번→4번)Hibernate:
select
s1_0.survey_document_id,
s1_0.accept_response,
s1_0.answer_count,
d1_0.date_id,
d1_0.survey_deadline,
d1_0.survey_enable,
d1_0.survey_start_date,
s1_0.survey_description,
d2_0.design_id,
d2_0.back_color,
d2_0.font,
d2_0.font_size,
s1_0.is_deleted,
s1_0.reliability,
s1_0.survey_title,
s1_0.survey_type,
s1_0.user_id
from
survey_document s1_0
left join
date_management d1_0
on s1_0.survey_document_id=d1_0.survey_document_id
left join
design d2_0
on s1_0.survey_document_id=d2_0.survey_document_id
where
s1_0.survey_document_id=?
and (
s1_0.is_deleted = 0
)
2023-10-18T00:30:14.584+09:00 INFO 20032 --- [io-8082-exec-10] p6spy : #1697556614584 | took 0ms | statement | connection 22| url jdbc:mysql://localhost:3306/surveydb
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d1_0.date_id,d1_0.survey_deadline,d1_0.survey_enable,d1_0.survey_start_date,s1_0.survey_description,d2_0.design_id,d2_0.back_color,d2_0.font,d2_0.font_size,s1_0.is_deleted,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join date_management d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join design d2_0 on s1_0.survey_document_id=d2_0.survey_document_id where s1_0.survey_document_id=? and (s1_0.is_deleted = 0)
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d1_0.date_id,d1_0.survey_deadline,d1_0.survey_enable,d1_0.survey_start_date,s1_0.survey_description,d2_0.design_id,d2_0.back_color,d2_0.font,d2_0.font_size,s1_0.is_deleted,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join date_management d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join design d2_0 on s1_0.survey_document_id=d2_0.survey_document_id where s1_0.survey_document_id=4 and (s1_0.is_deleted = 0);
2023-10-18T00:30:14.585+09:00 INFO 20032 --- [io-8082-exec-10] p6spy : #1697556614585 | took 0ms | commit | connection 22| url jdbc:mysql://localhost:3306/surveydb
;
Hibernate:
select
q1_0.survey_document_id,
q1_0.question_document_id,
q1_0.is_deleted,
q1_0.question_type,
q1_0.question_title
from
question_document q1_0
where
q1_0.survey_document_id=?
and (
q1_0.is_deleted = 0
)
2023-10-18T00:30:14.587+09:00 INFO 20032 --- [io-8082-exec-10] p6spy : #1697556614587 | took 0ms | statement | connection 22| url jdbc:mysql://localhost:3306/surveydb
select q1_0.survey_document_id,q1_0.question_document_id,q1_0.is_deleted,q1_0.question_type,q1_0.question_title from question_document q1_0 where q1_0.survey_document_id=? and (q1_0.is_deleted = 0)
select q1_0.survey_document_id,q1_0.question_document_id,q1_0.is_deleted,q1_0.question_type,q1_0.question_title from question_document q1_0 where q1_0.survey_document_id=4 and (q1_0.is_deleted = 0) ;
Hibernate:
select
c1_0.question_id,
c1_0.choice_id,
c1_0.choice_count,
c1_0.is_deleted,
c1_0.choice_title
from
choice c1_0
where
c1_0.question_id in(?,?)
and (
c1_0.is_deleted = 0
)
2023-10-18T00:30:14.591+09:00 INFO 20032 --- [io-8082-exec-10] p6spy : #1697556614591 | took 0ms | statement | connection 22| url jdbc:mysql://localhost:3306/surveydb
select c1_0.question_id,c1_0.choice_id,c1_0.choice_count,c1_0.is_deleted,c1_0.choice_title from choice c1_0 where c1_0.question_id in(?,?) and (c1_0.is_deleted = 0)
select c1_0.question_id,c1_0.choice_id,c1_0.choice_count,c1_0.is_deleted,c1_0.choice_title from choice c1_0 where c1_0.question_id in(14,15) and (c1_0.is_deleted = 0) ;
Hibernate:
select
w1_0.question_id,
w1_0.word_cloud_id,
w1_0.word_cloud_count,
w1_0.is_deleted,
w1_0.word_cloud_title
from
word_cloud w1_0
where
w1_0.question_id in(?,?)
and (
w1_0.is_deleted = 0
)
2023-10-18T00:30:14.601+09:00 INFO 20032 --- [io-8082-exec-10] p6spy : #1697556614601 | took 8ms | statement | connection 22| url jdbc:mysql://localhost:3306/surveydb
select w1_0.question_id,w1_0.word_cloud_id,w1_0.word_cloud_count,w1_0.is_deleted,w1_0.word_cloud_title from word_cloud w1_0 where w1_0.question_id in(?,?) and (w1_0.is_deleted = 0)
select w1_0.question_id,w1_0.word_cloud_id,w1_0.word_cloud_count,w1_0.is_deleted,w1_0.word_cloud_title from word_cloud w1_0 where w1_0.question_id in(14,15) and (w1_0.is_deleted = 0) ;
2023-10-18T00:30:14.601+09:00 INFO 20032 --- [io-8082-exec-10] c.e.s.s.service.SurveyDocumentService : SurveyDetailDto(id=4, title=test2, description=test2, countAnswer=0, questionList=[QuestionDetailDto(id=14, title=test, questionType=1, choiceList=[ChoiceDetailDto(id=15, title=test1, count=0), ChoiceDetailDto(id=16, title=test2, count=0)], wordCloudDtos=[]), QuestionDetailDto(id=15, title=test, questionType=1, choiceList=[ChoiceDetailDto(id=17, title=test3, count=0), ChoiceDetailDto(id=18, title=test4, count=0)], wordCloudDtos=[])], reliability=true, startDate=2023-10-02 00:27:17.1, endDate=2023-10-02 00:27:17.1, enable=true, design=DesignResponseDto(font=font, fontSize=0, backColor=font))
N + 1
문제가 완화되었으며 성능 향상도 있다.1.43
배 정도 속도가 빨라졌지만surveydocument
와 연관된 question
들을 fetch join
으로 가져오는 쿼리 하나question
들에 대해서 choice
들을 fetch join
으로 가져오는 쿼리 하나@Override
public SurveyDocument findSurveyById(Long surveyDocumentId) {
SurveyDocument survey = queryFactory
.selectFrom(surveyDocument)
.leftJoin(surveyDocument.design, design).fetchJoin()
.leftJoin(surveyDocument.date, dateManagement).fetchJoin()
.leftJoin(surveyDocument.questionDocumentList, questionDocument).fetchJoin()
.where(surveyDocument.id.eq(surveyDocumentId))
.fetchOne();
if (survey != null && survey.getQuestionDocumentList() != null) {
List<QuestionDocument> questionDocuments = queryFactory
.selectFrom(questionDocument)
.leftJoin(questionDocument.choiceList, choice).fetchJoin()
.where(questionDocument.in(survey.getQuestionDocumentList()))
.fetch();
}
return survey;
}
Hibernate:
select
s1_0.survey_document_id,
s1_0.accept_response,
s1_0.answer_count,
d2_0.date_id,
d2_0.survey_deadline,
d2_0.survey_enable,
d2_0.survey_start_date,
s1_0.survey_description,
d1_0.design_id,
d1_0.back_color,
d1_0.font,
d1_0.font_size,
s1_0.is_deleted,
q1_0.survey_document_id,
q1_0.question_document_id,
q1_0.is_deleted,
q1_0.question_type,
q1_0.question_title,
s1_0.reliability,
s1_0.survey_title,
s1_0.survey_type,
s1_0.user_id
from
survey_document s1_0
left join
design d1_0
on s1_0.survey_document_id=d1_0.survey_document_id
left join
date_management d2_0
on s1_0.survey_document_id=d2_0.survey_document_id
left join
question_document q1_0
on s1_0.survey_document_id=q1_0.survey_document_id
and (
q1_0.is_deleted = 0
)
and (
q1_0.is_deleted = 0
)
where
(
s1_0.is_deleted = 0
)
and s1_0.survey_document_id=?
2023-10-18T01:02:28.500+09:00 INFO 12756 --- [io-8082-exec-10] p6spy : #1697558548500 | took 0ms | statement | connection 9| url jdbc:mysql://localhost:3306/surveydb
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d2_0.date_id,d2_0.survey_deadline,d2_0.survey_enable,d2_0.survey_start_date,s1_0.survey_description,d1_0.design_id,d1_0.back_color,d1_0.font,d1_0.font_size,s1_0.is_deleted,q1_0.survey_document_id,q1_0.question_document_id,q1_0.is_deleted,q1_0.question_type,q1_0.question_title,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join design d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join date_management d2_0 on s1_0.survey_document_id=d2_0.survey_document_id left join question_document q1_0 on s1_0.survey_document_id=q1_0.survey_document_id and (q1_0.is_deleted = 0) and (q1_0.is_deleted = 0) where (s1_0.is_deleted = 0) and s1_0.survey_document_id=?
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d2_0.date_id,d2_0.survey_deadline,d2_0.survey_enable,d2_0.survey_start_date,s1_0.survey_description,d1_0.design_id,d1_0.back_color,d1_0.font,d1_0.font_size,s1_0.is_deleted,q1_0.survey_document_id,q1_0.question_document_id,q1_0.is_deleted,q1_0.question_type,q1_0.question_title,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join design d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join date_management d2_0 on s1_0.survey_document_id=d2_0.survey_document_id left join question_document q1_0 on s1_0.survey_document_id=q1_0.survey_document_id and (q1_0.is_deleted = 0) and (q1_0.is_deleted = 0) where (s1_0.is_deleted = 0) and s1_0.survey_document_id=4;
Hibernate:
select
q1_0.question_document_id,
c1_0.question_id,
c1_0.choice_id,
c1_0.choice_count,
c1_0.is_deleted,
c1_0.choice_title,
q1_0.is_deleted,
q1_0.question_type,
q1_0.survey_document_id,
q1_0.question_title
from
question_document q1_0
left join
choice c1_0
on q1_0.question_document_id=c1_0.question_id
and (
c1_0.is_deleted = 0
)
and (
c1_0.is_deleted = 0
)
where
(
q1_0.is_deleted = 0
)
and q1_0.question_document_id in(?,?)
2023-10-18T01:02:28.505+09:00 INFO 12756 --- [io-8082-exec-10] p6spy : #1697558548505 | took 1ms | statement | connection 9| url jdbc:mysql://localhost:3306/surveydb
select q1_0.question_document_id,c1_0.question_id,c1_0.choice_id,c1_0.choice_count,c1_0.is_deleted,c1_0.choice_title,q1_0.is_deleted,q1_0.question_type,q1_0.survey_document_id,q1_0.question_title from question_document q1_0 left join choice c1_0 on q1_0.question_document_id=c1_0.question_id and (c1_0.is_deleted = 0) and (c1_0.is_deleted = 0) where (q1_0.is_deleted = 0) and q1_0.question_document_id in(?,?)
select q1_0.question_document_id,c1_0.question_id,c1_0.choice_id,c1_0.choice_count,c1_0.is_deleted,c1_0.choice_title,q1_0.is_deleted,q1_0.question_type,q1_0.survey_document_id,q1_0.question_title from question_document q1_0 left join choice c1_0 on q1_0.question_document_id=c1_0.question_id and (c1_0.is_deleted = 0) and (c1_0.is_deleted = 0) where (q1_0.is_deleted = 0) and q1_0.question_document_id in(14,15);
Hibernate:
select
w1_0.question_id,
w1_0.word_cloud_id,
w1_0.word_cloud_count,
w1_0.is_deleted,
w1_0.word_cloud_title
from
word_cloud w1_0
where
w1_0.question_id in(?,?)
and (
w1_0.is_deleted = 0
)
2023-10-18T01:02:28.510+09:00 INFO 12756 --- [io-8082-exec-10] p6spy : #1697558548510 | took 1ms | statement | connection 9| url jdbc:mysql://localhost:3306/surveydb
select w1_0.question_id,w1_0.word_cloud_id,w1_0.word_cloud_count,w1_0.is_deleted,w1_0.word_cloud_title from word_cloud w1_0 where w1_0.question_id in(?,?) and (w1_0.is_deleted = 0)
select w1_0.question_id,w1_0.word_cloud_id,w1_0.word_cloud_count,w1_0.is_deleted,w1_0.word_cloud_title from word_cloud w1_0 where w1_0.question_id in(14,15) and (w1_0.is_deleted = 0) ;
즉, 결론을 내리자면
hibernate.default_batch_fetch_size
를 글로벌 설정으로 사용해 N+1
문제를 최대한 in
쿼리로 기본적인 성능을 보장하게 한다.@OneToOne, @ManyToOne
과 같이 1
관계의 자식 엔티티에 대해서는 모두 Fetch Join
을 적용하여 한방 쿼리를 수행한다.@OneToMany, @ManyToMany
와 같이 N
관계의 자식 엔티티에 관해서는 가장 데이터가 많은 자식쪽에 Fetch Join
을 사용한다.Fetch Join
이 없는 자식 엔티티에 관해서는 위에서 선언한 hibernate.default_batch_fetch_size
적용으로 100~1000
개의 in
쿼리로 성능을 보장한다. @Override
public SurveyDocument findSurveyById(Long surveyDocumentId) {
// SurveyDocument 조회 시 design, date에 대해서는 fetchJoin을 사용
// questionDocumentList에 대해서도 fetchJoin을 사용하여 한 번의 쿼리로 로딩
SurveyDocument survey = queryFactory
.selectFrom(surveyDocument)
.leftJoin(surveyDocument.design, design).fetchJoin()
.leftJoin(surveyDocument.date, dateManagement).fetchJoin()
.leftJoin(surveyDocument.questionDocumentList, questionDocument).fetchJoin()
.where(surveyDocument.id.eq(surveyDocumentId))
.fetchOne();
if (survey != null && survey.getQuestionDocumentList() != null) {
// questionDocumentList가 존재하는 경우, 해당 questionDocument의 choiceList를 fetchJoin으로 로딩
// questionDocument에 대한 추가 쿼리 실행
List<QuestionDocument> questionDocuments = queryFactory
.selectFrom(questionDocument)
.leftJoin(questionDocument.choiceList, choice).fetchJoin()
// 여기서 wordCloud는 fetchJoin을 사용하지 않고, hibernate.default_batch_fetch_size 설정에 의존
.where(questionDocument.in(survey.getQuestionDocumentList()))
.fetch();
// Hibernate의 default_batch_fetch_size 설정에 의존하여 wordCloud를 로딩
}
return survey;
}
6
번batchsize:1000
적용: 쿼리 총 4
번3
번OneToMany
관계의 entity를 Querydsl로 조회할 때 fetchjoin
을 사용하면 데이터가 중복되어 조회될 수 있다.distinct
를 추가하여 중복된 row를 제거할 수 있다. 이는 SQL 수준에서는 중복된 로우를 제거하지만, 실제로는 데이터베이스에서 모든 중복된 결과를 가져온 후, 애플리케이션 메모리 내에서 엔티티의 중복을 제거하기 때문에 완전히 중복 조회를 피한다고는 할 수 없다. @Override
public Optional<SurveyDocument> findSurveyById(Long surveyDocumentId) {
SurveyDocument survey = queryFactory
.selectFrom(surveyDocument)
.leftJoin(surveyDocument.design, design).fetchJoin()
.leftJoin(surveyDocument.date, dateManagement).fetchJoin()
.leftJoin(surveyDocument.questionDocumentList, questionDocument).fetchJoin()
.where(surveyDocument.id.eq(surveyDocumentId))
.distinct()
.fetchOne();
if (survey != null && survey.getQuestionDocumentList() != null) {
List<QuestionDocument> questionDocuments = queryFactory
.selectFrom(questionDocument)
.leftJoin(questionDocument.choiceList, choice).fetchJoin()
.where(questionDocument.in(survey.getQuestionDocumentList()))
.distinct()
.fetch();
}
return Optional.ofNullable(survey);
}
@QueryProjection
{
"startDate": "2023-10-01T15:27:17.100+00:00",
"endDate": "2023-10-01T15:27:17.100+00:00",
"enable": true
}
Hibernate:
select
s1_0.survey_document_id,
s1_0.accept_response,
s1_0.answer_count,
d1_0.date_id,
d1_0.survey_deadline,
d1_0.survey_enable,
d1_0.survey_start_date,
s1_0.survey_description,
d2_0.design_id,
d2_0.back_color,
d2_0.font,
d2_0.font_size,
s1_0.is_deleted,
s1_0.reliability,
s1_0.survey_title,
s1_0.survey_type,
s1_0.user_id
from
survey_document s1_0
left join
date_management d1_0
on s1_0.survey_document_id=d1_0.survey_document_id
left join
design d2_0
on s1_0.survey_document_id=d2_0.survey_document_id
where
s1_0.survey_document_id=?
and (
s1_0.is_deleted = 0
)
2023-10-18T01:53:55.051+09:00 INFO 19692 --- [io-8082-exec-10] p6spy : #1697561635051 | took 1ms | statement | connection 22| url jdbc:mysql://localhost:3306/surveydb
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d1_0.date_id,d1_0.survey_deadline,d1_0.survey_enable,d1_0.survey_start_date,s1_0.survey_description,d2_0.design_id,d2_0.back_color,d2_0.font,d2_0.font_size,s1_0.is_deleted,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join date_management d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join design d2_0 on s1_0.survey_document_id=d2_0.survey_document_id where s1_0.survey_document_id=? and (s1_0.is_deleted = 0)
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d1_0.date_id,d1_0.survey_deadline,d1_0.survey_enable,d1_0.survey_start_date,s1_0.survey_description,d2_0.design_id,d2_0.back_color,d2_0.font,d2_0.font_size,s1_0.is_deleted,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join date_management d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join design d2_0 on s1_0.survey_document_id=d2_0.survey_document_id where s1_0.survey_document_id=4 and (s1_0.is_deleted = 0);
2023-10-18T01:53:55.067+09:00 INFO 19692 --- [io-8082-exec-10] p6spy
사용되는 코드가 db에서 findById
로 설문을 조회하고 거기서 필요한 데이터 3개를 뽑아서 DTO
에 넣어서 반환하고 있었다.
이 코드를 최적화 하기 위해 db에서 바로 dto
로 조회하는 방법으로 수정할 것이다.
이 방법은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법이다.
다만 DTO
에 QueryDSL
어노테이션을 유지해야 하는 점과 DTO
까지 Q 파일
을 생성해야 하는 단점이 있다.
QueryDsl
수정
@Override
public ManagementResponseDto findManageById(Long surveyDocumentId) {
return queryFactory.select(new QManagementResponseDto(dateManagement.startDate, dateManagement.deadline, dateManagement.isEnabled))
.from(dateManagement)
.where(dateManagement.surveyDocument.id.eq(surveyDocumentId))
.fetchOne();
}
Hibernate:
select
d1_0.survey_start_date,
d1_0.survey_deadline,
d1_0.survey_enable
from
date_management d1_0
where
d1_0.survey_document_id=?
직접 업데이트 쿼리
설문의 활성/비활성화는 survey
에 연결된 DateManagement
의 is Enabled
만 바꿔주면 된다.
하지만 지금은 불필요하게 많은 데이터를 가져와서 update
를 해주고 있다.
보통 JPA 사용 시 엔티티를 먼저 조회(fetch
)한 후, 해당 엔티티의 상태를 변경하고 트랜잭션 커밋 시점에서 변경 감지(dirty checking
)를 통해 SQL Update
문이 실행되는 방식을 사용한다.
하지만 이 경우, 불필요하게 엔티티를 메모리에 로딩하는 상황이 발생할 수 있다. 특히, 엔티티가 복잡하거나 연관 관계가 많은 경우에는 성능 저하의 원인이 될 수 있다.
수정 전 조회 쿼리
Hibernate:
select
s1_0.survey_document_id,
s1_0.accept_response,
s1_0.answer_count,
d1_0.date_id,
d1_0.survey_deadline,
d1_0.survey_enable,
d1_0.survey_start_date,
s1_0.survey_description,
d2_0.design_id,
d2_0.back_color,
d2_0.font,
d2_0.font_size,
s1_0.is_deleted,
s1_0.reliability,
s1_0.survey_title,
s1_0.survey_type,
s1_0.user_id
from
survey_document s1_0
left join
date_management d1_0
on s1_0.survey_document_id=d1_0.survey_document_id
left join
design d2_0
on s1_0.survey_document_id=d2_0.survey_document_id
where
s1_0.survey_document_id=?
and (
s1_0.is_deleted = 0
)
2023-10-18T02:32:54.241+09:00 INFO 3588 --- [nio-8082-exec-1] p6spy : #1697563974241 | took 1ms | statement | connection 9| url jdbc:mysql://localhost:3306/surveydb
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d1_0.date_id,d1_0.survey_deadline,d1_0.survey_enable,d1_0.survey_start_date,s1_0.survey_description,d2_0.design_id,d2_0.back_color,d2_0.font,d2_0.font_size,s1_0.is_deleted,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join date_management d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join design d2_0 on s1_0.survey_document_id=d2_0.survey_document_id where s1_0.survey_document_id=? and (s1_0.is_deleted = 0)
select s1_0.survey_document_id,s1_0.accept_response,s1_0.answer_count,d1_0.date_id,d1_0.survey_deadline,d1_0.survey_enable,d1_0.survey_start_date,s1_0.survey_description,d2_0.design_id,d2_0.back_color,d2_0.font,d2_0.font_size,s1_0.is_deleted,s1_0.reliability,s1_0.survey_title,s1_0.survey_type,s1_0.user_id from survey_document s1_0 left join date_management d1_0 on s1_0.survey_document_id=d1_0.survey_document_id left join design d2_0 on s1_0.survey_document_id=d2_0.survey_document_id where s1_0.survey_document_id=4 and (s1_0.is_deleted = 0);
Hibernate:
update
date_management
set
survey_deadline=?,
survey_enable=?,
survey_start_date=?,
survey_document_id=?
where
date_id=?
2023-10-18T02:32:54.244+09:00 INFO 3588 --- [nio-8082-exec-1] p6spy : #1697563974244 | took 1ms | statement | connection 9| url jdbc:mysql://localhost:3306/surveydb
update date_management set survey_deadline=?, survey_enable=?, survey_start_date=?, survey_document_id=? where date_id=?
update date_management set survey_deadline='2023-10-02T00:27:17.100+0900', survey_enable=true, survey_start_date='2023-10-02T00:27:17.100+0900', survey_document_id=4 where date_id=7;
2023-10-18T02:32:54.290+09:00 INFO 3588 --- [nio-8082-exec-1] p6spy : #1697563974290 | took 44ms | commit | connection 9| url jdbc:mysql://localhost:3306/surveydb
@Override
@Transactional
public void updateManage(Long id, Boolean enable) {
queryFactory.update(dateManagement)
.where(dateManagement.surveyDocument.id.eq(id))
.set(dateManagement.isEnabled, enable)
.execute();
}
Hibernate:
update
date_management
set
survey_enable=?
where
survey_document_id=?
2023-10-18T02:45:33.646+09:00 INFO 16500 --- [nio-8082-exec-3] p6spy : #1697564733646 | took 1ms | statement | connection 17| url jdbc:mysql://localhost:3306/surveydb
update date_management set survey_enable=? where survey_document_id=?
update date_management set survey_enable=true where survey_document_id=4;
select
로 불필요한 정보를 가져올 필요도 없어졌다.Querydsl
의 update
메서드를 사용하여, 필요한 엔티티의 상태만을 직접 변경하는 방식을 채택하였다.