-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUserNicknameConcurrencyTest.java
More file actions
129 lines (114 loc) Β· 5.53 KB
/
Copy pathUserNicknameConcurrencyTest.java
File metadata and controls
129 lines (114 loc) Β· 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package org.runnect.server.user.service;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.runnect.server.user.dto.request.UpdateUserNicknameRequestDto;
import org.runnect.server.user.entity.RunnectUser;
import org.runnect.server.user.entity.SocialType;
import org.runnect.server.user.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
/**
* updateUserNickname()μ "λλ€μ μ€λ³΅ μ‘°ν -> μμΌλ©΄ λ³κ²½" μμλ‘ λμνλλ°, μλ‘ λ€λ₯Έ λ μ μ κ°
* λμμ κ°μ μ λλ€μμΌλ‘ λ³κ²½μ μμ²νλ©΄ λ λ€ "μ€λ³΅ μμ"μ λ³΄κ³ κ°μ λ³κ²½μ μλν μ μλ€.
* nickname 컬λΌμ unique μ μ½μΌλ‘ DBκ° νλλ κ±°λΆνλλ°, μμ μ μλ μν°ν° νλλ§ λ°κΎΈλ
* λν° μ²΄νΉμ΄λΌ μ΄ λ©μλ μμμ flushλ₯Ό κ°μ νμ§ μμΌλ©΄ μλ° μ¬λΆκ° νΈλμμ
μ»€λ° μμ μμΌ
* DataIntegrityViolationExceptionμΌλ‘ κ·Έλλ‘ μλ€(μ€μ λ‘컬 Postgresμ λν΄ μ¬νν΄ νμΈν λ€
* μ»€λ° λ‘κ·Έμ λ¨κΉ). μμ νμλ saveAndFlushλ‘ λ©μλ μμμ μ§μ ν°λ¨λ € μ‘κ³
* DuplicateNicknameException(409)μΌλ‘ λ³ννλ€ β μ΄ ν
μ€νΈλ κ·Έ μμ ν λμμ κ²μ¦νλ€.
*/
@SpringBootTest
class UserNicknameConcurrencyTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Autowired
private PlatformTransactionManager transactionManager;
@PersistenceContext
private EntityManager entityManager;
private Long userId1;
private Long userId2;
@AfterEach
void tearDown() {
TransactionTemplate tx = new TransactionTemplate(transactionManager);
tx.executeWithoutResult(status -> {
entityManager.createQuery("DELETE FROM UserStamp s WHERE s.runnectUser.id IN :ids")
.setParameter("ids", java.util.List.of(userId1, userId2))
.executeUpdate();
if (userId1 != null) {
userRepository.deleteById(userId1);
}
if (userId2 != null) {
userRepository.deleteById(userId2);
}
});
}
@Test
void λμμ_μλ‘_λ€λ₯Έ_μ μ κ°_κ°μ_λλ€μμΌλ‘_λ³κ²½νλ©΄_νμͺ½λ§_μ±κ³΅νλ€() throws InterruptedException {
TransactionTemplate tx = new TransactionTemplate(transactionManager);
userId1 = tx.execute(status -> userRepository.save(
RunnectUser.builder()
.nickname("cc-nick-race-1")
.socialId("concurrency-test-social-id-nick-1")
.email("concurrency-test-nick-1@runnect.test")
.provider(SocialType.VISITOR)
.build()
).getId());
userId2 = tx.execute(status -> userRepository.save(
RunnectUser.builder()
.nickname("cc-nick-race-2")
.socialId("concurrency-test-social-id-nick-2")
.email("concurrency-test-nick-2@runnect.test")
.provider(SocialType.VISITOR)
.build()
).getId());
String targetNickname = "λ μ΄μ€λλ€μ";
int threadCount = 2;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch readyLatch = new CountDownLatch(threadCount);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(threadCount);
AtomicReference<Throwable> capturedException = new AtomicReference<>();
Long[] userIds = {userId1, userId2};
for (int i = 0; i < threadCount; i++) {
Long targetUserId = userIds[i];
executor.submit(() -> {
try {
readyLatch.countDown();
startLatch.await();
userService.updateUserNickname(targetUserId,
new UpdateUserNicknameRequestDto(targetNickname));
} catch (Throwable e) {
capturedException.compareAndSet(null, e);
} finally {
doneLatch.countDown();
}
});
}
readyLatch.await();
startLatch.countDown();
boolean completed = doneLatch.await(15, TimeUnit.SECONDS);
executor.shutdown();
assertThat(completed).withFailMessage("μ€λ λκ° μ ν μκ° λ΄μ λλμ§ μμ").isTrue();
Throwable exception = capturedException.get();
assertThat(exception)
.withFailMessage("λμ λλ€μ λ³κ²½ μμ² μ€ νλκ° μ€ν¨ν κ²μΌλ‘ μμνμ§λ§ λ λ€ μ±κ³΅ν¨")
.isNotNull();
assertThat(exception)
.withFailMessage(
"μμ μ μλ DataIntegrityViolationExceptionμ΄ κ·Έλλ‘ μμ. μμ ν μμ: DuplicateNicknameException(409). μ€μ : %s",
exception
)
.isInstanceOf(org.runnect.server.user.exception.userException.DuplicateNicknameException.class);
}
}