Skip to content

Commit

Permalink
[feat/CK-237] 골룸 참여 시 발생하는 동시성 이슈를 해결한다 (#199)
Browse files Browse the repository at this point in the history
* refactor: 골룸 참여 시 발생하는 동시성 이슈를 비관적 락으로 해결

* test: 테스트 코드 수정
  • Loading branch information
miseongk authored Dec 2, 2023
1 parent 359926a commit 2944699
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

public interface GoalRoomQueryRepository {

Optional<GoalRoom> findGoalRoomByIdWithPessimisticLock(Long goalRoomId);

Optional<GoalRoom> findByIdWithRoadmapContent(final Long goalRoomId);

Optional<GoalRoom> findByIdWithContentAndTodos(final Long goalRoomId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import co.kirikiri.persistence.goalroom.dto.RoadmapGoalRoomsOrderType;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.BooleanExpression;
import jakarta.persistence.LockModeType;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
Expand All @@ -27,6 +28,16 @@ public GoalRoomQueryRepositoryImpl() {
super(GoalRoom.class);
}

@Override
public Optional<GoalRoom> findGoalRoomByIdWithPessimisticLock(final Long goalRoomId) {
return Optional.ofNullable(selectFrom(goalRoom)
.innerJoin(goalRoom.goalRoomPendingMembers.values, goalRoomPendingMember)
.fetchJoin()
.where(goalRoom.id.eq(goalRoomId))
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne());
}

@Override
public Optional<GoalRoom> findByIdWithRoadmapContent(final Long goalRoomId) {
return Optional.ofNullable(selectFrom(goalRoom)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package co.kirikiri.persistence.goalroom;

import co.kirikiri.domain.goalroom.GoalRoom;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface GoalRoomRepository extends JpaRepository<GoalRoom, Long>, GoalRoomQueryRepository {

@Override

Optional<GoalRoom> findById(final Long goalRoomId);

List<GoalRoom> findAllByEndDate(final LocalDate endDate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ private Member findMemberByIdentifier(final String memberIdentifier) {

public void join(final String identifier, final Long goalRoomId) {
final Member member = findMemberByIdentifier(identifier);
final GoalRoom goalRoom = findGoalRoomById(goalRoomId);
final GoalRoom goalRoom = findGoalRoomByIdWithPessimisticLock(goalRoomId);
goalRoom.join(member);
}

private GoalRoom findGoalRoomById(final Long goalRoomId) {
return goalRoomRepository.findById(goalRoomId)
private GoalRoom findGoalRoomByIdWithPessimisticLock(final Long goalRoomId) {
return goalRoomRepository.findGoalRoomByIdWithPessimisticLock(goalRoomId)
.orElseThrow(() -> new NotFoundException("존재하지 않는 골룸입니다. goalRoomId = " + goalRoomId));
}

Expand All @@ -139,6 +139,11 @@ public Long addGoalRoomTodo(final Long goalRoomId, final String identifier,
return goalRoom.findLastGoalRoomTodo().getId();
}

private GoalRoom findGoalRoomById(final Long goalRoomId) {
return goalRoomRepository.findById(goalRoomId)
.orElseThrow(() -> new NotFoundException("존재하지 않는 골룸입니다. goalRoomId = " + goalRoomId));
}

private void checkGoalRoomCompleted(final GoalRoom goalRoom) {
if (goalRoom.isCompleted()) {
throw new BadRequestException("이미 종료된 골룸입니다.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@
import io.restassured.common.mapper.TypeRef;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockMultipartFile;
import java.io.IOException;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockMultipartFile;

class GoalRoomCreateIntegrationTest extends InitIntegrationTest {

Expand Down Expand Up @@ -290,37 +290,6 @@ class GoalRoomCreateIntegrationTest extends InitIntegrationTest {
);
}

@Test
void 모집_중이지_않은_골룸에_참가_요청을_보내면_예외가_발생한다() throws IOException {
//given
final Long 기본_로드맵_아이디 = 로드맵_생성(기본_로드맵_생성_요청, 기본_로그인_토큰);
final RoadmapResponse 로드맵_응답 = 로드맵을_아이디로_조회하고_응답객체를_반환한다(기본_로드맵_아이디);

final List<GoalRoomRoadmapNodeRequest> 골룸_노드_별_기간_요청 = List.of(
new GoalRoomRoadmapNodeRequest(로드맵_응답.content().nodes().get(0).id(), 정상적인_골룸_노드_인증_횟수, 오늘, 십일_후));
final GoalRoomCreateRequest 골룸_생성_요청 = new GoalRoomCreateRequest(기본_로드맵_아이디, 정상적인_골룸_이름, 정상적인_골룸_제한_인원,
골룸_노드_별_기간_요청);
final Long 골룸_아이디 = 골룸을_생성하고_아이디를_반환한다(골룸_생성_요청, 기본_로그인_토큰);
골룸을_시작한다(기본_로그인_토큰, 골룸_아이디);

final MemberJoinRequest 팔로워_회원_가입_요청 = new MemberJoinRequest("identifier2", "paswword2@",
"follower", GenderType.FEMALE, DEFAULT_EMAIL);
final LoginRequest 팔로워_로그인_요청 = new LoginRequest(팔로워_회원_가입_요청.identifier(), 팔로워_회원_가입_요청.password());
회원가입(팔로워_회원_가입_요청);
final String 팔로워_액세스_토큰 = String.format(BEARER_TOKEN_FORMAT, 로그인(팔로워_로그인_요청).accessToken());

//when
final ExtractableResponse<Response> 참가_요청에_대한_응답 = 골룸_참가_요청(골룸_아이디, 팔로워_액세스_토큰);

//then
final String 예외_메시지 = 참가_요청에_대한_응답.asString();

assertAll(
() -> assertThat(참가_요청에_대한_응답.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()),
() -> assertThat(예외_메시지).contains("모집 중이지 않은 골룸에는 참여할 수 없습니다.")
);
}

@Test
void 인증_피드_등록을_요청한다() throws IOException {
//given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ static void setUp() {

when(memberRepository.findByIdentifier(any()))
.thenReturn(Optional.of(follower));
when(goalRoomRepository.findById(anyLong()))
when(goalRoomRepository.findGoalRoomByIdWithPessimisticLock(anyLong()))
.thenReturn(Optional.of(goalRoom));

//when
Expand Down Expand Up @@ -282,7 +282,7 @@ static void setUp() {

when(memberRepository.findByIdentifier(any()))
.thenReturn(Optional.of(follower));
when(goalRoomRepository.findById(anyLong()))
when(goalRoomRepository.findGoalRoomByIdWithPessimisticLock(anyLong()))
.thenReturn(Optional.empty());

//when, then
Expand All @@ -304,7 +304,7 @@ static void setUp() {

when(memberRepository.findByIdentifier(any()))
.thenReturn(Optional.of(follower));
when(goalRoomRepository.findById(anyLong()))
when(goalRoomRepository.findGoalRoomByIdWithPessimisticLock(anyLong()))
.thenReturn(Optional.of(goalRoom));

//when, then
Expand All @@ -326,7 +326,7 @@ static void setUp() {

when(memberRepository.findByIdentifier(any()))
.thenReturn(Optional.of(follower));
when(goalRoomRepository.findById(anyLong()))
when(goalRoomRepository.findGoalRoomByIdWithPessimisticLock(anyLong()))
.thenReturn(Optional.of(goalRoom));

//when, then
Expand Down

0 comments on commit 2944699

Please sign in to comment.