2525import java .util .concurrent .CountDownLatch ;
2626import java .util .concurrent .ExecutorService ;
2727import java .util .concurrent .Executors ;
28+ import java .util .concurrent .atomic .AtomicInteger ;
2829
2930import static org .assertj .core .api .Assertions .assertThatThrownBy ;
3031
@@ -112,7 +113,7 @@ void addLikeOnBookLogWithManyUserSimultaneously() throws InterruptedException {
112113 final List <User > users = testUserFactory .createNUser (10 , "test@test.com" , "password" , "이름" , 1 );
113114 final BookLog bookLog = testBookLogFactory .createBookLog (book , users .get (0 ));
114115
115- int threadCount = 10 ;
116+ final int threadCount = 10 ;
116117 final ExecutorService executorService = Executors .newFixedThreadPool (threadCount );
117118 final CountDownLatch countDownLatch = new CountDownLatch (threadCount );
118119
@@ -135,6 +136,45 @@ void addLikeOnBookLogWithManyUserSimultaneously() throws InterruptedException {
135136 softAssertions .assertThat (bookLogLikeByBookLogId ).hasSize (10 );
136137 });
137138 }
139+
140+ @ DisplayName ("같은 유저가 같은 BookLog에 좋아요를 동시에 2번 추가할 경우 1번만 추가된다." )
141+ @ Test
142+ void addLikeOnBookLogWithConcurrentSameUser () throws InterruptedException {
143+ // given
144+ final Book book = testBookFactory .createBook ("책" , "작가" , "출판사" );
145+ final User user = testUserFactory .createUser ("test@test.com" , "password" , "이름" , 1 );
146+ final BookLog bookLog = testBookLogFactory .createBookLog (book , user );
147+
148+ final User anotherUser = testUserFactory .createUser ("test2@test.com" , "password" , "이름" , 1 );
149+
150+ final int threadCount = 2 ;
151+ final ExecutorService executorService = Executors .newFixedThreadPool (threadCount );
152+ final CountDownLatch countDownLatch = new CountDownLatch (threadCount );
153+
154+ // when
155+ final AtomicInteger errorCount = new AtomicInteger (0 );
156+ for (int i = 0 ; i < threadCount ; i ++) {
157+ executorService .submit (() -> {
158+ try {
159+ bookLogLikeService .addLike (bookLog .getId (), anotherUser .getId ());
160+ } catch (Exception ignored ) {
161+ errorCount .incrementAndGet ();
162+ } finally {
163+ countDownLatch .countDown ();
164+ }
165+ });
166+ }
167+ countDownLatch .await ();
168+
169+ // then
170+ final BookLog likedBookLog = bookLogRepository .findById (bookLog .getId ()).get ();
171+ final List <BookLogLike > bookLogLikeByBookLogId = bookLogLikeRepository .findAllByBookLogId (bookLog .getId ());
172+ SoftAssertions .assertSoftly (softAssertions -> {
173+ softAssertions .assertThat (likedBookLog .getLikeCount ()).isEqualTo (1 );
174+ softAssertions .assertThat (bookLogLikeByBookLogId ).hasSize (1 );
175+ softAssertions .assertThat (errorCount .get ()).isEqualTo (threadCount - 1 );
176+ });
177+ }
138178 }
139179
140180 @ DisplayName ("BookLog 좋아요 취소" )
@@ -203,7 +243,7 @@ void deleteLikeOnBookLogWithManyUserSimultaneously() throws InterruptedException
203243 final BookLog bookLog = testBookLogFactory .createBookLog (book , users .get (0 ));
204244 testBookLogLikeFactory .createNBookLogLike (bookLog .getId (), users );
205245
206- int threadCount = 10 ;
246+ final int threadCount = 10 ;
207247 final ExecutorService executorService = Executors .newFixedThreadPool (threadCount );
208248 final CountDownLatch countDownLatch = new CountDownLatch (threadCount );
209249
@@ -224,5 +264,46 @@ void deleteLikeOnBookLogWithManyUserSimultaneously() throws InterruptedException
224264 softAssertions .assertThat (bookLogLikeByBookLogId ).hasSize (0 );
225265 });
226266 }
267+
268+ @ DisplayName ("같은 유저가 BookLog에 좋아요 취소를 동시에 두번 하더라도 1번만 취소 처리된다." )
269+ @ Test
270+ void deleteLikeOnBookLogWithConcurrentSameUser () throws InterruptedException {
271+ // given
272+ final Book book = testBookFactory .createBook ("책" , "작가" , "출판사" );
273+ final User user = testUserFactory .createUser ("test@test.com" , "password" , "이름" , 1 );
274+ final BookLog bookLog = testBookLogFactory .createBookLog (book , user );
275+
276+ final User anotherUser = testUserFactory .createUser ("test2@test.com" , "password" , "이름" , 1 );
277+ testBookLogLikeFactory .createNBookLogLike (bookLog .getId (), List .of (anotherUser ));
278+
279+ final int threadCount = 2 ;
280+ final ExecutorService executorService = Executors .newFixedThreadPool (threadCount );
281+ final CountDownLatch countDownLatch = new CountDownLatch (threadCount );
282+
283+ // when
284+ final AtomicInteger errorCount = new AtomicInteger (0 );
285+ for (int i = 0 ; i < threadCount ; i ++) {
286+ executorService .submit (() -> {
287+ try {
288+ bookLogLikeService .deleteLike (bookLog .getId (), anotherUser .getId ());
289+ } catch (Exception ignored ) {
290+ errorCount .incrementAndGet ();
291+ } finally {
292+ countDownLatch .countDown ();
293+ }
294+
295+ });
296+ }
297+ countDownLatch .await ();
298+
299+ // then
300+ final BookLog notLikedBookLog = bookLogRepository .findById (bookLog .getId ()).get ();
301+ final List <BookLogLike > bookLogLikeByBookLogId = bookLogLikeRepository .findAllByBookLogId (bookLog .getId ());
302+ SoftAssertions .assertSoftly (softAssertions -> {
303+ softAssertions .assertThat (notLikedBookLog .getLikeCount ()).isEqualTo (0 );
304+ softAssertions .assertThat (bookLogLikeByBookLogId ).hasSize (0 );
305+ softAssertions .assertThat (errorCount .get ()).isEqualTo (threadCount - 1 );
306+ });
307+ }
227308 }
228309}
0 commit comments