๊ฒฐ๋ก ๋ถํฐ ๋งํ์๋ฉด @Transactional์ ๋์ ๋ฐฉ์ ๋๋ฌธ์ด๋ค.
๋ฌธ์ ์ํฉ
์ฌ๊ณ ๋ฅผ ํ๋ ๊ฐ์์ํค๋ ์์ฒญ์ด ๋์์ 100๊ฐ๊ฐ ๋ค์ด์จ ์ํฉ์ด๋ค.
๋น๊ด์ ๋ฝ์ ์ฌ์ฉํด์ Race condition์ ํผํ๋ คํ๋ค.
์ด๋ ํ ์คํธ ์ฝ๋์ @Transactional์ ๋ถ์ด๋ฉด Lock wait timeout exceeded๊ฐ ๋ฐ์ํ๋ฉฐ ํ ์คํธ์ ์คํจํ๋ค.
@SpringBootTest
@Transactional
class StockServiceTest {
@Test
public void ๋์์_100๊ฐ์_์์ฒญ() throws InterruptedException {
int threadCount = 100;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
stockService.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
// 100 - (1 * 100) = 0
assertEquals(0, stock.getQuantity());
}
}
@Service
public class PessimisticLockStockService {
@Transactional
public void decrease(Long id, Long quantity) {
Stock stock = stockRepository.findByIdWithPessimisticLock(id);
stock.decrease(quantity);
stockRepository.save(stock);
}
}
์์ธ
@Transactional์ ๋ถ์ด๋ฉด ํ๋ก์ ํด๋์ค๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด๋ค.
@Transactoinal์ ์ํด ๋ค์๊ณผ ๊ฐ์ ํ๋ก์ ํด๋์ค๊ฐ ๋ง๋ค์ด์ง๊ณ ๋์ํ๋ค.
- ์ค๋ ๋ A๊ฐ ๋ฝ์ ์ทจ๋ํ๋ค.
- ์ค๋ ๋ B๋ ๋ฝ์ด ์ข ๋ฃ๋ ๋๊น์ง ๋๊ธฐํ๋ค.
- ๋ฝ์ ํด์ ํ๋ ค๋ฉด ๋ฉ์๋๊ฐ ์ข ๋ฃ๋์ด์ผ ํ๋๋ฐ ์ค๋ ๋ B๊ฐ ๋๊ธฐ ์ค์ด๋ผ ๋ฉ์๋๊ฐ ์ข ๋ฃ๋์ง ์๋๋ค. (์ด๋ ์ข ๋ฃ๋์ง ์๋ ๋ฉ์๋๋ ์๋ณธ ClubService์ join ๋ฉ์๋๊ฐ ์๋ ํ๋ก์ ํด๋์ค์ join ๋ฉ์๋๋ค.)
- ๊ณ์ ๋๊ธฐํ๋ค๊ฐ Lock timeout์ด ๋ฐ์ํ์ฌ ํ ์คํธ์ ์คํจํ๋ค.
class ClubServiceProxy (clubService: ClubService, transaction: TransactionManager) {
func join(sessionUser : Long, key: Long) {
transaction.start()
try {
clubService.join(id, quantity)
transaction.commit()
} catch (e: Exeption)
transaction.rollback()
}
}
}
ํด๊ฒฐ๋ฐฉ๋ฒ
@Transactional์ ์ ๊ฑฐํ๊ณ @AfterEach๋ก ๋ฐ์ดํฐ๋ฅผ Rollbackํ๋ค.
๋๋ ํ ์คํธ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋งค๋ฒ ์ด๊ธฐํํด์ ์ฌ์ฉํ๋๋ก application.yml์ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ์๋ ์๋ค.
jpa:
hibernate:
ddl-auto: create
+ ํ ์คํธ ์ฝ๋์ @Transactional์ ์ฌ์ฉํ ์ด์
๋ฐ์ดํฐ๋ฅผ ๋กค๋ฐฑํ๋ ค๋ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉํ๋ค.
ํ๋ก์ ํด๋์ค๋ฅผ ๋ง๋ค์ด ๋์ํ๋ค๋ ๊ฑธ ์ ์ํด๋ฌ์ผ๊ฒ ๋ค
+ ๋น๊ด์ ๋ฝ์ ์ฌ์ฉํ ์ด์
์ถฉ๋์ด ๋น๋ฒํ๊ฒ ์ผ์ด๋ ์ ์๋ ์ํฉ์ด๋ผ๊ณ ํ๋จํด ๋น๊ด์ ๋ฝ์ ์ฌ์ฉํ๋ค.
๋๊ด์ ๋ฝ์ ๋ณ๋์ lock์ ์ก์ง ์๊ณ retry ๋ก์ง์ผ๋ก ๋์์ฑ์ ์ ์ดํ๋ค. ๋ฐ๋ผ์ 100๊ฐ์ ์ฌ๊ณ ๊ฐ์ ์์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ง์ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋๋ค. ๋ฐ๋ฉด ๋น๊ด์ ๋ฝ์ lock์ ๊ฑธ๊ณ ์์ ์ ์ํํ๋ฉฐ, ์ดํ ์์ฒญ๋ค์ ๋ฝ์ด ํด์ ๋๊ธธ ๊ธฐ๋ค๋ฆฌ๋ฏ๋ก ๋ง์ ์์ฒญ์ ํ์ง ์๋๋ค.
+ ๋น๊ด์ ๋ฝ์ ์ฌ์ฉํ ๋, ๋ฝ์ด ํด์ ๋์๋์ง ์ด๋ป๊ฒ ์๊น?
๋น๊ด์ ๋ฝ์ ๋ฝ์ด ์ด์ฉ๊ฐ๋ฅํ ์ํ์ธ์ง๋ฅผ ๊ณ์ํด์ ํ์ธํ๋ ๋ก์ง์ด ์๋ค. ๊ทธ๋ฌ๋ฉด ๋ฝ์ด ์ด์ฉ๊ฐ๋ฅํ์ง๋ฅผ ์ด๋ป๊ฒ ๊ฐ์งํ๋๊ฑธ๊น.
๋ฝ์ด ์ด์ฉ๊ฐ๋ฅํ์ง๋ ํธ๋์ญ์ ์ด ์ข ๋ฃ๋๋ฉด ์ ์ ์๋ค! ํธ๋์ญ์ ์ commit ๋๋ rollback์ ์ํด ์ข ๋ฃ๋๋ค. ์ด๋ commit, rollback์ ๋ชจ๋ ๋ฝ์ ํด์ ํ๋ค. ๋ฐ๋ผ์ ๋น๊ด์ ๋ฝ์ ํธ๋์ญ์ ์ ์ข ๋ฃ ์ฌ๋ถ์ ๋ฐ๋ผ ๋ฝ ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ํ๋จํ ์ ์๊ฒ ๋๋ค.
'Spring ๐ฑ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
JWT ์ธ์ฆโ์ธ๊ฐ ๊ตฌํํ๊ธฐ (0) | 2024.02.11 |
---|---|
๋์์ฑ ์ด์ ํด๊ฒฐํ๊ธฐ : synchronized, MySQL Lock, Redis Lock (1) | 2024.02.11 |
๋์์ฑ ์ด์ ํด๊ฒฐํ๊ธฐ : synchronized์ @Transactional์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ ์ด์ (0) | 2023.12.15 |
๋์์ฑ ์ด์ ํด๊ฒฐํ๊ธฐ : synchronized์ saveAndFlush (0) | 2023.12.01 |
UserSesseion ์ฌ๋ก๋ํ๊ธฐ (0) | 2023.11.03 |