inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

주문 조회 V3.1: 엔티티를 DTO로 변환 - 페이징과 한계 돌파

16:10 페이징 쿼리, mvc 관련 질문드립니다.

해결된 질문

282

딸기초콜릿

작성한 질문수 8

0

학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.

안녕하세요 영한님 jpa 강의 정말 재미있게 잘 듣고 있습니다. 이번에 페이징 한계돌파 강의를 들으면서 실습을 하던 중 16:10쯤에 저의 개발환경에서 발생한 쿼리가 영한님의 개발환경에서 발생한 쿼리와 다른 것 같아서 질문드립니다. where __row__ >= 2 and __row__ < 102; 쿼리로 페이징이 적용되어 결과는 원하는 대로 나왔지만, 예상과는 조금 다르게 아래와 같은 서브 쿼리가 나갔습니다. 데이터베이스 방언 문제는 아닌 것 같고 jpa 버전 문제일까요?
 
2022-05-04 01:14:49.015 DEBUG 21484 --- [nio-8081-exec-6] org.hibernate.SQL : with query as (select inner_query.*, row_number() over ( order by current_timestamp) as __row__ from ( select order0_.order_id as order_id1_6_0_, member1_.member_id as member_i1_4_1_, delivery2_.delivery_id as delivery1_2_2_, order0_.delivery_id as delivery4_6_0_, order0_.member_id as member_i5_6_0_, order0_.order_date as order_da2_6_0_, order0_.order_status as order_st3_6_0_, member1_.city as city2_4_1_, member1_.street as street3_4_1_, member1_.zip_code as zip_code4_4_1_, member1_.name as name5_4_1_, delivery2_.city as city2_2_2_, delivery2_.street as street3_2_2_, delivery2_.zip_code as zip_code4_2_2_, delivery2_.status as status5_2_2_ from orders order0_ inner join member member1_ on order0_.member_id=member1_.member_id inner join delivery delivery2_ on order0_.delivery_id=delivery2_.delivery_id ) inner_query ) select order_id1_6_0_, member_i1_4_1_, delivery1_2_2_, delivery4_6_0_, member_i5_6_0_, order_da2_6_0_, order_st3_6_0_, city2_4_1_, street3_4_1_, zip_code4_4_1_, name5_4_1_, city2_2_2_, street3_2_2_, zip_code4_2_2_, status5_2_2_ from query where __row__ >= ? and __row__ < ? 2022-05-04 01:14:49.035 INFO 21484 --- [nio-8081-exec-6] p6spy : #1651594489035 | took 3ms | statement | connection 8| url jdbc:h2:tcp://localhost/~/jpashopping with query as (select inner_query.*, row_number() over (order by current_timestamp) as __row__ from ( select order0_.order_id as order_id1_6_0_, member1_.member_id as member_i1_4_1_, delivery2_.delivery_id as delivery1_2_2_, order0_.delivery_id as delivery4_6_0_, order0_.member_id as member_i5_6_0_, order0_.order_date as order_da2_6_0_, order0_.order_status as order_st3_6_0_, member1_.city as city2_4_1_, member1_.street as street3_4_1_, member1_.zip_code as zip_code4_4_1_, member1_.name as name5_4_1_, delivery2_.city as city2_2_2_, delivery2_.street as street3_2_2_, delivery2_.zip_code as zip_code4_2_2_, delivery2_.status as status5_2_2_ from orders order0_ inner join member member1_ on order0_.member_id=member1_.member_id inner join delivery delivery2_ on order0_.delivery_id=delivery2_.delivery_id ) inner_query ) select order_id1_6_0_, member_i1_4_1_, delivery1_2_2_, delivery4_6_0_, member_i5_6_0_, order_da2_6_0_, order_st3_6_0_, city2_4_1_, street3_4_1_, zip_code4_4_1_, name5_4_1_, city2_2_2_, street3_2_2_, zip_code4_2_2_, status5_2_2_ from query where __row__ >= ? and __row__ < ? with query as (select inner_query.*, row_number() over (order by current_timestamp) as __row__ from ( select order0_.order_id as order_id1_6_0_, member1_.member_id as member_i1_4_1_, delivery2_.delivery_id as delivery1_2_2_, order0_.delivery_id as delivery4_6_0_, order0_.member_id as member_i5_6_0_, order0_.order_date as order_da2_6_0_, order0_.order_status as order_st3_6_0_, member1_.city as city2_4_1_, member1_.street as street3_4_1_, member1_.zip_code as zip_code4_4_1_, member1_.name as name5_4_1_, delivery2_.city as city2_2_2_, delivery2_.street as street3_2_2_, delivery2_.zip_code as zip_code4_2_2_, delivery2_.status as status5_2_2_ from orders order0_ inner join member member1_ on order0_.member_id=member1_.member_id inner join delivery delivery2_ on order0_.delivery_id=delivery2_.delivery_id ) inner_query ) select order_id1_6_0_, member_i1_4_1_, delivery1_2_2_, delivery4_6_0_, member_i5_6_0_, order_da2_6_0_, order_st3_6_0_, city2_4_1_, street3_4_1_, zip_code4_4_1_, name5_4_1_, city2_2_2_, street3_2_2_, zip_code4_2_2_, status5_2_2_ from query where __row__ >= 2 and __row__ < 102; 2022-05-04 01:14:49.038 DEBUG 21484 --- [nio-8081-exec-6] org.hibernate.SQL : select orderitems0_.order_id as order_id5_5_0_, orderitems0_.order_item_id as order_it1_5_0_, orderitems0_.order_item_id as order_it1_5_1_, orderitems0_.count as count2_5_1_, orderitems0_.item_id as item_id4_5_1_, orderitems0_.order_id as order_id5_5_1_, orderitems0_.order_price as order_pr3_5_1_ from order_item orderitems0_ where orderitems0_.order_id=? 2022-05-04 01:14:49.040 INFO 21484 --- [nio-8081-exec-6] p6spy : #1651594489040 | took 0ms | statement | connection 8| url jdbc:h2:tcp://localhost/~/jpashopping select orderitems0_.order_id as order_id5_5_0_, orderitems0_.order_item_id as order_it1_5_0_, orderitems0_.order_item_id as order_it1_5_1_, orderitems0_.count as count2_5_1_, orderitems0_.item_id as item_id4_5_1_, orderitems0_.order_id as order_id5_5_1_, orderitems0_.order_price as order_pr3_5_1_ from order_item orderitems0_ where orderitems0_.order_id=? select orderitems0_.order_id as order_id5_5_0_, orderitems0_.order_item_id as order_it1_5_0_, orderitems0_.order_item_id as order_it1_5_1_, orderitems0_.count as count2_5_1_, orderitems0_.item_id as item_id4_5_1_, orderitems0_.order_id as order_id5_5_1_, orderitems0_.order_price as order_pr3_5_1_ from order_item orderitems0_ where orderitems0_.order_id=11; 2022-05-04 01:14:49.044 DEBUG 21484 --- [nio-8081-exec-6] org.hibernate.SQL : select item0_.item_id as item_id2_3_0_, item0_.name as name3_3_0_, item0_.price as price4_3_0_, item0_.stock_quantity as stock_qu5_3_0_, item0_.artist as artist6_3_0_, item0_.etc as etc7_3_0_, item0_.author as author8_3_0_, item0_.isbn as isbn9_3_0_, item0_.actor as actor10_3_0_, item0_.director as directo11_3_0_, item0_.dtype as dtype1_3_0_ from item item0_ where item0_.item_id=? 2022-05-04 01:14:49.046 INFO 21484 --- [nio-8081-exec-6] p6spy : #1651594489046 | took 0ms | statement | connection 8| url jdbc:h2:tcp://localhost/~/jpashopping select item0_.item_id as item_id2_3_0_, item0_.name as name3_3_0_, item0_.price as price4_3_0_, item0_.stock_quantity as stock_qu5_3_0_, item0_.artist as artist6_3_0_, item0_.etc as etc7_3_0_, item0_.author as author8_3_0_, item0_.isbn as isbn9_3_0_, item0_.actor as actor10_3_0_, item0_.director as directo11_3_0_, item0_.dtype as dtype1_3_0_ from item item0_ where item0_.item_id=? select item0_.item_id as item_id2_3_0_, item0_.name as name3_3_0_, item0_.price as price4_3_0_, item0_.stock_quantity as stock_qu5_3_0_, item0_.artist as artist6_3_0_, item0_.etc as etc7_3_0_, item0_.author as author8_3_0_, item0_.isbn as isbn9_3_0_, item0_.actor as actor10_3_0_, item0_.director as directo11_3_0_, item0_.dtype as dtype1_3_0_ from item item0_ where item0_.item_id=9; 2022-05-04 01:14:49.048 DEBUG 21484 --- [nio-8081-exec-6] org.hibernate.SQL : select item0_.item_id as item_id2_3_0_, item0_.name as name3_3_0_, item0_.price as price4_3_0_, item0_.stock_quantity as stock_qu5_3_0_, item0_.artist as artist6_3_0_, item0_.etc as etc7_3_0_, item0_.author as author8_3_0_, item0_.isbn as isbn9_3_0_, item0_.actor as actor10_3_0_, item0_.director as directo11_3_0_, item0_.dtype as dtype1_3_0_ from item item0_ where item0_.item_id=? 2022-05-04 01:14:49.050 INFO 21484 --- [nio-8081-exec-6] p6spy : #1651594489050 | took 0ms | statement | connection 8| url jdbc:h2:tcp://localhost/~/jpashopping select item0_.item_id as item_id2_3_0_, item0_.name as name3_3_0_, item0_.price as price4_3_0_, item0_.stock_quantity as stock_qu5_3_0_, item0_.artist as artist6_3_0_, item0_.etc as etc7_3_0_, item0_.author as author8_3_0_, item0_.isbn as isbn9_3_0_, item0_.actor as actor10_3_0_, item0_.director as directo11_3_0_, item0_.dtype as dtype1_3_0_ from item item0_ where item0_.item_id=? select item0_.item_id as item_id2_3_0_, item0_.name as name3_3_0_, item0_.price as price4_3_0_, item0_.stock_quantity as stock_qu5_3_0_, item0_.artist as artist6_3_0_, item0_.etc as etc7_3_0_, item0_.author as author8_3_0_, item0_.isbn as isbn9_3_0_, item0_.actor as actor10_3_0_, item0_.director as directo11_3_0_, item0_.dtype as dtype1_3_0_ from item item0_ where item0_.item_id=10;

또한 api가 아닌 mvc 패턴으로 컬렉션을 find할 때에는 dto가 아닌 엔티티를 그대로 repository에서 찾아서 view 화면에 넘기는데, 이 때는 쿼리 성능 최적화를 어떻게 할 수 있을까요? (타임리프로 order, orderItem의 결과를 보여준다고 가정할 때) 

 

java spring-boot spring JPA

답변 1

1

김영한

안녕하세요. 딸기초콜릿님

데이터베이스 방언 때문에 페이징 쿼리 최적화를 위해 해당 구문이 사용됩니다^^ 데이터베이스 방언을 변경해보시면 결과가 달라지는 것을 확인하실 수 있을거에요.

 

추가로 API가 아니어도 fetch join, batch size 등을 활용해서 최적화가 가능합니다.

감사합니다.

0

딸기초콜릿

방언 문제였군요... 답변 정말 감사합니다 영한님 ~ 

영한님의 강의에서 배운 것들을 토대로 간단하게 게시판을 만들고 있는데 게시물(1)과 댓글(다)을 일대다 관계로 설정했습니다. 홈 화면에서는 일반적인 카페 게시판처럼 페이징을 통해서 [ 게시판 pk, 게시판 제목, 해당 게시판 댓글 수, 게시판 조회수 ] x 10개를 띄우는데 아무래도 댓글은 게시물의 입장에서 일대다 관계이므로 페치 조인으로 하면 페이징이 안 되는 것으로 알고 있습니다. batch size를 글로벌 설정으로 넣어두고 실행했는데도 in 쿼리가 안 나가네요... ㅜ 

그래서 하단처럼 강제 초기화를 하거나 api 강의에서 배운 것처럼 dto로 가져와서 해결은 했지만 뭔가 이게 맞는 방식인지는 잘 모르겠네요 ㅎ...

 

강제 초기화

List<Article> articles = articleService.findArticlesByPageDesc();

articles.forEach(article -> article.getReplies().forEach(
reply -> reply.getId()
));

 

0

김영한

딸기초콜릿님

API에 엔티티를 반환하셨을까요?

강의에서 설명드린 것 처럼 API에 엔티티를 직접 반환하면 안되고, DTO로 변환해서 반환하셔야 합니다.

감사합니다.

0

딸기초콜릿

안녕하세요 영한님 mvc 패턴에서 dto로 만들거나 하는게 문제가 아니였네요...

 

 @GetMapping("/")
public String home(@SessionAttribute(name = "memberId", required = false) Long memberId,
Model model) {

List<Article> articles = articleService.findArticlesByPageDesc();

// articles.forEach(article -> article.getReplies().forEach(
// reply -> reply.getId()
// ));

model.addAttribute("articles", articles);

if (memberId == null) {
return "home";
}

Member member = memberService.findById(memberId);
model.addAttribute("member", member);

return "loginHome";
}

이렇게 실행하면 home 화면에서는 댓글을 불러오는 쿼리가 batch_fetch_size로 in 쿼리로 최적화되지만, loginHome 화면에서는 이상하게 최적화가 되지 않네요... 

Member member = memberService.findById(memberId);
model.addAttribute("member", member);

이 코드가 articleService.findArticles한 결과의 영속성 컨텍스트에 영향을 미치는 것인가요?

물론 주석을 해제해서 게시물의 댓글 불러오는 쿼리를 강제로 초기화하면 모두 in 쿼리로 최적화 됩니다. 

 

0

딸기초콜릿

안녕하세요 영한님 혹시나 해서 memberServiceImpl단에서 findById(Long memberId)의 트랜잭션을 읽기전용으로 변경했더니 바로 최적화가 되네요~

0

김영한

안녕하세요. 딸기초콜릿님

다음 이슈인 것 같아요.

https://www.inflearn.com/questions/427216

감사합니다.

@JsonIgnore 이후 Internal Server Error가 발생하지 않습니다.

0

34

2

강의 관련 외 질문입니다.

0

91

2

SpringBoot4 + Hibernate7 모듈 등록 방법 공유

1

114

1

BeanCreationException

0

105

3

Update 후 UpdateMemberResponse 매핑할 때

0

67

1

트랜잭션을 사용 안 할 때 커넥션은 언제 가져오나요?

0

114

2

페이징 + 검색조건 관련해서 질문드립니다.

0

80

1

Query Dsl Q파일 질문입니다.

0

93

1

루트 쿼리라는것은

0

69

1

메서드를 분리하는 기준

0

77

1

findAllWithMemberDelivery 메서드 질문드립니다.

0

127

3

연관관계 매핑을 안 쓸 경우, 사용해야 하는 전략

0

98

2

fetch join과 영속화와 OSIV의 관계

0

104

2

Distinct 사용 전 결과에 대한 의문

0

127

2

레포지토리 계층에서의 트랜잭션에 대한 의문

0

66

1

영속성 컨텍스트 생명주기의 신기한 부분이 있습니다.

0

86

2

dto 필드 속 엔티티 여부

0

71

1

뷰템플릿 사용 시

0

88

2

Result 클래스 관련 질문

0

60

1

@PostConstruct 프록시 관련 질문드립니다

0

91

1

DTO 대신 Form 사용은 안되나요?

0

144

1

OSIV ON 상태일 때

0

103

1

fetch join VS fetch join 페이징 궁금증

0

196

2

양방향 연관관계 알아보는 법?

0

113

1