From 66433db18236f0cc7a090125f880fbc23098beae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9B=90=EA=B2=BD?= Date: Wed, 31 Jul 2024 16:07:47 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/service/TimetableService.java | 20 ++++++--- .../koin/acceptance/TimetableApiTest.java | 41 +++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/timetable/service/TimetableService.java b/src/main/java/in/koreatech/koin/domain/timetable/service/TimetableService.java index b68b0e490..7d43c130b 100644 --- a/src/main/java/in/koreatech/koin/domain/timetable/service/TimetableService.java +++ b/src/main/java/in/koreatech/koin/domain/timetable/service/TimetableService.java @@ -5,6 +5,9 @@ import java.util.Objects; import java.util.Optional; +import in.koreatech.koin.global.exception.RequestTooFastException; +import jakarta.persistence.EntityManager; +import jakarta.persistence.OptimisticLockException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,6 +39,7 @@ public class TimetableService { private final TimetableFrameRepositoryV2 timetableFrameRepositoryV2; private final SemesterRepositoryV2 semesterRepositoryV2; private final UserRepository userRepository; + private final EntityManager entityManager; public List getLecturesBySemester(String semester) { List lectures = lectureRepositoryV2.findBySemester(semester); @@ -111,13 +115,17 @@ public TimetableResponse getTimetables(Integer userId, String semesterRequest) { @Transactional public void deleteTimetableLecture(Integer userId, Integer timetableLectureId) { - TimetableLecture timetableLecture = timetableLectureRepositoryV2.getById(timetableLectureId); - TimetableFrame frame = timetableFrameRepositoryV2.getById(timetableLecture.getTimetableFrame().getId()); - if (!Objects.equals(frame.getUser().getId(), userId)) { - throw AuthorizationException.withDetail("userId: " + userId); + try { + TimetableLecture timetableLecture = timetableLectureRepositoryV2.getById(timetableLectureId); + TimetableFrame frame = timetableFrameRepositoryV2.getById(timetableLecture.getTimetableFrame().getId()); + if (!Objects.equals(frame.getUser().getId(), userId)) { + throw AuthorizationException.withDetail("userId: " + userId); + } + timetableLectureRepositoryV2.deleteById(timetableLectureId); + entityManager.flush(); + } catch (OptimisticLockException e) { + throw new RequestTooFastException("요청이 너무 빠릅니다. 다시 시도해주세요."); } - - timetableLectureRepositoryV2.deleteById(timetableLectureId); } private TimetableResponse getTimetableResponse(Integer userId, TimetableFrame timetableFrame) { diff --git a/src/test/java/in/koreatech/koin/acceptance/TimetableApiTest.java b/src/test/java/in/koreatech/koin/acceptance/TimetableApiTest.java index 4b14f1a39..47f2c3f09 100644 --- a/src/test/java/in/koreatech/koin/acceptance/TimetableApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/TimetableApiTest.java @@ -584,4 +584,45 @@ void deleteTimetable() { assertThat(timetableRepository.findById(2)).isNotPresent(); } + + @Test + @DisplayName("시간표 삭제 동시성 예외 적절하게 처리하는지 테스트한다.") + void deleteTimetableConcurrency() throws InterruptedException { + User user = userFixture.준호_학생().getUser(); + String token = userFixture.getToken(user); + Semester semester = semesterFixture.semester("20192"); + + Lecture 건축구조의_이해_및_실습 = lectureFixture.건축구조의_이해_및_실습(semester.getSemester()); + Lecture HRD_개론 = lectureFixture.HRD_개론(semester.getSemester()); + + timetableV2Fixture.시간표6(user, semester, 건축구조의_이해_및_실습, HRD_개론); + + ExecutorService executor = Executors.newFixedThreadPool(2); + CountDownLatch latch = new CountDownLatch(2); + + List responseList = new ArrayList<>(); + Runnable deleteTask = () -> { + Response response = RestAssured + .given() + .header("Authorization", "Bearer " + token) + .when() + .param("id", 2) + .delete("/timetable"); + responseList.add(response); + latch.countDown(); + }; + + executor.submit(deleteTask); + executor.submit(deleteTask); + + latch.await(); + + boolean hasConflict = responseList.stream() + .anyMatch(response -> response.getStatusCode() == 409); + + assertThat(hasConflict).isTrue(); + assertThat(timetableRepository.findById(2)).isNotPresent(); + + executor.shutdown(); + } } From 7dee87ff0ec9b6e80d17e6d765eddd9d7ea0de36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9B=90=EA=B2=BD?= Date: Wed, 31 Jul 2024 16:36:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=ED=8C=A8=ED=82=A4=EC=A7=80=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/acceptance/TimetableApiTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/in/koreatech/koin/acceptance/TimetableApiTest.java b/src/test/java/in/koreatech/koin/acceptance/TimetableApiTest.java index 47f2c3f09..5ddbda842 100644 --- a/src/test/java/in/koreatech/koin/acceptance/TimetableApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/TimetableApiTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.restassured.response.Response; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -20,6 +21,12 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + @SuppressWarnings("NonAsciiCharacters") class TimetableApiTest extends AcceptanceTest {