요약
- member관련 중복된 쿼리 문제를 해결하였다.
- 각각 작동하던 메서드를 하나의 트랜잭션으로 묶어주었다.
문제점
- 처음에 member관련 쿼리가 2번 발생함
- username을 select하는 쿼리, memberId를 select하는 쿼리
- username관련 쿼리는 jwt인증관련해서 나온 쿼리였음
- memberId관련 쿼리는 conversationService의 addMember 메서드 때문에 발생한 쿼리였음
- @AuthenticationPrincipal 때문인 줄 알았으나, 해당 애너테이션은 SecurityContext에서 member객체를 가져오는 것이며
DB관련 쿼리와는 관련없음
- @AuthenticationPrincipal 때문인 줄 알았으나, 해당 애너테이션은 SecurityContext에서 member객체를 가져오는 것이며
- PostConversation의 반환값으로 conversation뿐만 아니라 categoryList도 필요하다. 지금은 service단에서 conversation과 categoryList를 따로 가공해서 반환한다. 전체의 과정이 하나의 트랜잭션으로 묶이는 것이 맞다고 판단했고 따라서 controller에서 메서드 두번을 사용하던 것을 한 번으로 수정해야한다.
수정 전 생각
- conversation을 인스턴스화 하고, memberID를 추가해주고, conversationId로 conversation을 찾아서 requestAnswer메서드 매개변수로 conversation을 넣어준다.
- conversation과 category를 아우르는 새로운 클래스를 만든다.
- 처음에는 service단에서만 처리해서 반환한다. → conversation과 category 전혀 다른 엔티티를 한 번에 반환할 수는 없다.
과정
1
대화 세션을 생성하면 다음과 같은 sql쿼리가 발생한다.
//-------------------------해결할 부분-------------------------
// 1
Hibernate:
select
memberenti0_.id as id1_5_,
memberenti0_.avatar_link as avatar_l2_5_,
memberenti0_.created_at as created_3_5_,
memberenti0_.password as password4_5_,
memberenti0_.user_id as user_id5_5_,
memberenti0_.username as username6_5_
from
member memberenti0_
where
memberenti0_.username=?
// 2
Hibernate:
select
memberenti0_.id as id1_5_0_,
memberenti0_.avatar_link as avatar_l2_5_0_,
memberenti0_.created_at as created_3_5_0_,
memberenti0_.password as password4_5_0_,
memberenti0_.user_id as user_id5_5_0_,
memberenti0_.username as username6_5_0_
from
member memberenti0_
where
memberenti0_.id=?
//-----------------------------------------------------------------
Hibernate:
insert
into
conversation
(id, activity_level, answer_summary, bookmarked, created_at, delete_status, member_id, modified_at, pinned, published, saved, tagged, title, view_count)
values
(default, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate:
select
qna0_.id as id1_6_,
qna0_.answer as answer2_6_,
qna0_.bookmark_status as bookmark3_6_,
qna0_.conversation_id as conversa5_6_,
qna0_.question as question4_6_
from
qna qna0_
where
qna0_.conversation_id=?
Hibernate:
insert
into
qna
(id, answer, bookmark_status, conversation_id, question)
values
(default, ?, ?, ?, ?)
// requestAnswer 메서드 끝
Hibernate:
update
conversation
set
activity_level=?,
answer_summary=?,
bookmarked=?,
created_at=?,
delete_status=?,
member_id=?,
modified_at=?,
pinned=?,
published=?,
saved=?,
tagged=?,
title=?,
view_count=?
where
id=?
Hibernate:
select
category0_.id as id1_1_,
category0_.member_id as member_i2_1_,
category0_.name as name3_1_
from
category category0_
where
category0_.member_id=?
and (
category0_.id not in (
?
)
)
다른 쿼리문들을 좀 더 최적화 할 수 있을지는 잘 모르겠지만, 일단 제일 처음의 member관련 쿼리가 중복되는 문제는 지금 충분히 해결할 수 있다.
username으로 select하는 첫 번째 쿼리문은 jwt로 Authentication을 설정하는 필터에서 발생하였고,
memberId로 select하는 두 번째 쿼리문은 대화세션을 생성하는 service클래스의 메서드에서 발생하였다. 그 메서드 아래와 같다.
@Transactional
public Conversation createConversation(long memberId, QnADto.Post dto)
{
Conversation conversation = new Conversation();
conversation.addMember(memberRepository.findById(memberId).orElse(null)); // 이 부분
Conversation savedConversation = conversationRepository.save(conversation);
long conversationId = savedConversation.getId();
dto.setConversationId(conversationId);
qnaService.requestAnswer(dto);
return conversationRepository.save(conversation);
}
member엔티티와 conversation엔티티는 부모-자식 관계이기 때문에 addMember메서드로 member객체를 찾아 추가해주었는데, 이 부분때문에 중복된 쿼리가 발생하는 것이었다.
현 로직에서 member객체에서 필요한 정보는 없으며, 만약 있더라도 SecurityContext에서 불러오면 굳이 DB를 사용할 필요가 없다.
수정한 코드는 다음과 같다.
public Conversation createConversation(long memberId, ConversationDto.Post dto)
{
Conversation conversation = new Conversation();
conversation.addMember(new MemberEntity(memberId)); // 수정
Conversation savedConversation = conversationRepository.save(conversation);
dto.setConversation(savedConversation);
qnaService.requestAnswer(savedConversation, dto.getQuestion());
return conversationRepository.save(conversation);
}
2
@PostMapping
public ResponseEntity generateConversation(@RequestBody ConversationDto.Post dto,
@AuthenticationPrincipal MemberEntity member)
{
Long memberId = member.getId();
Conversation savedConversation = conversationService.createConversation(memberId, dto);
ConversationDto.Response response = conversationService.getConversationAndCategoryList(savedConversation, memberId);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
controller에서 service 메서드를 각각 호출하고 있다.
새로운 대화세션을 생성하는 기능을 하나의 트랜잭션으로 만들고 싶어 아래와 같이 코드를 수정하였다.
@PostMapping
public ResponseEntity generateConversation(@RequestBody ConversationDto.Post dto,
@AuthenticationPrincipal MemberEntity member)
{
Long memberId = member.getId();
ConversationDto.Response response = conversationService.createConversation(memberId, dto);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
---
@Transactional
public ConversationDto.Response createConversation(long memberId, ConversationDto.Post dto)
{
...
}
controller에서는 하나의 메서드만 호출하고, service클래스에서는 @transactional 애너테이션을 추가하여 해당 기능이 하나의 트랜잭션으로 동작하도록 하였다.
'프로젝트' 카테고리의 다른 글
| JPA n+1 문제 (0) | 2023.06.21 |
|---|---|
| 지연 로딩(Lazy loading), 영속성 컨텍스트 (0) | 2023.06.18 |
| 대화방 제목/pin 수정 - fetchType과 DTO (0) | 2023.06.14 |
| 프로젝트 개선 (0) | 2023.06.09 |