π‘ μ΅μμ©λμ μ¬κ³ μμ€ν μΌλ‘ μμ보λ λμμ±μ΄μ ν΄κ²°λ°©λ² κ°μλ₯Ό λ£κ³ μ 리ν λ΄μ©μ λλ€.
μ΄λ€ μμ μ΄ μ€λ κ±Έλ € μλΉμ€ μλ΅ μλμ μν₯μ μ£Όλ κ²½μ°, μ ν리μΌμ΄μ μ μ±λ₯ ν₯μμ μν΄ λ©ν° μ€λ λ νκ²½μ κ³ λ €ν μ μλ€. λ©ν° μ€λ λλ₯Ό ν΅ν΄ μ¬λ¬ μμ μ€λ λκ° λμμ μ€νλμ΄ μλ΅ μλμ μ²λ¦¬λμ κ°μ ν μ μλ€. κ·Έλ¬λ, μ¬λ¬ μ€λ λκ° λμμ λμΌν λ°μ΄ν°μ μ κ·Όνκ³ μμ ν κ²½μ° λ°μ΄ν° μΌκ΄μ±μ΄ κΉ¨μ§κ±°λ λ μ΄μ€ 컨λμ (Race Condition)κ³Ό κ°μ λ¬Έμ κ° λ°μν μ μλ€. μ΄λ μμ μ΄ μλν λλ‘ μ²λ¦¬λμ§ μκ² λ§λ€μ΄, λ°μ΄ν° 무결μ±μ μμμν¬ μνμ΄ μλ€.
κ°μμμλ μν μ¬κ³ λ₯Ό κ΄λ¦¬νλ κ°λ¨ν μ¬κ³ μμ€ν μ ꡬννμ¬ λ©ν° μ€λ λ νκ²½μμ λ°μνλ λμμ± λ¬Έμ λ₯Ό λ§μ£Όνκ³ λ°μ μμΈμ λΆμνλ€. ν΄κ²° λ°©μμΌλ‘λ 3κ°μ§ λ°©λ²μ μ°¨λ‘λλ‘ μ μ©νμ¬ ν΄κ²°ν΄λ³Έλ€.
첫λ²μ§Έ λ°©λ²μ Application levelμμ μλ°μ synchronized ν€μλλ₯Ό μ¬μ©νμ¬ ν΄κ²°νλ κ²μ΄κ³ , λλ²μ§Έλ Databaseκ° μ 곡νλ Lock, λ§μ§λ§μΌλ‘ μΈλ²μ§Έλ λ λμ€(Redis)λ‘ λΆμ° λ½(Distributed lock)μ ꡬννμ¬ ν΄κ²°νλ κ²μ΄λ€.
μ΄λ² κΈμμλ λ©ν° μ€λ λ νκ²½μμ κ°λ°ν λ λ°μνλ λ¬Έμ μ κ³Ό λ°μ μμΈμ μ΄ν΄λ³΄κ³ λ¨Όμ Application levelμμ μλ°μ synchronized ν€μλλ₯Ό μ¬μ©νμ¬ ν΄κ²°ν΄λ³Ό κ²μ΄λ€.
μ¬κ³ κ°μ λ‘μ§ μμ±
κ°λ¨ν μ¬κ³ κ΄λ¦¬ μμ€ν μ ꡬννλ€. λ¨Όμ μ¬κ³ κ°μ λ‘μ§μ μμ±νλ€.
Stock
λ°μ΄ν°λ² μ΄μ€μ μ¬κ³ μ 보λ₯Ό λ΄κ³ μλ ν μ΄λΈκ³Ό λ§€μΉ λλ μν°ν°λ€.
@Entity
public class Stock {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long productId;
private Long quantity;
public Stock() {
}
public Stock(Long id, Long quantity) {
this.id = id;
this.quantity = quantity;
}
public Long getQuantity() {
return quantity;
}
public void decrease(Long quantity){
if(this.quantity - quantity < 0){
throw new RuntimeException("μ¬κ³ λ 0κ° λ―Έλ§μ΄ λ μ μμ΅λλ€.");
}
// νμ¬ μλμ κ°±μ
this.quantity -= quantity;
}
}
StockRepository
// StockRepository : μ¬κ³ (Stock)μ λν λ°μ΄ν°λ² μ΄μ€μμ CRUD κΈ°λ₯ μ 곡
public interface StockRepository extends JpaRepository<Stock, Long> {
}
StockService
μ¬κ³ μλΉμ€μ κ΄ν΄ ν΅μ¬ λΉμ¦λμ€ λ‘μ§μ΄ μ‘΄μ¬νλ€.
μ¬κ³ κ°μ λ‘μ§μΈ decrease() λ©μλλ₯Ό ꡬννλ€.
@Service
public class StockService {
private final StockRepository stockRepository;
public StockService(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
// μ¬κ³ κ°μ λ‘μ§ κ΅¬ν
@Transactional
public void decrease(Long id, Long quantity) {
// Stock μ‘°ν
Optional<Stock> stock = stockRepository.findById(id);
// μ¬κ³ κ°μ
stock.orElseThrow(() -> new RuntimeException("ν΄λΉνλ μ¬κ³ κ° μμ΅λλ€."))
.decrease(quantity);
// κ°±μ λ κ°μ μ μ₯
stockRepository.save(stock.get());
}
}
StockServiceTest
@SpringBootTest
class StockServiceTest {
@Autowired
private StockService stockService;
@Autowired
private StockRepository stockRepository;
// κ° ν
μ€νΈκ° μ€νλκΈ° μ μ λ°μ΄ν°λ² μ΄μ€μ ν
μ€νΈ λ°μ΄ν° μμ±
@BeforeEach
public void before() {
stockRepository.saveAndFlush(new Stock(1L, 100L));
}
// κ° ν
μ€νΈλ₯Ό μ€νν νμ λ°μ΄ν°λ² μ΄μ€μ ν
μ€νΈ λ°μ΄ν° μμ
@AfterEach
public void after() {
stockRepository.deleteAll();
}
@Test
public void μ¬κ³ κ°μ() {
// when
stockService.decrease(1L, 1L);
// then : 1L μνμ μ¬κ³ : 100 - 1 = 99κ°κ° λ¨μμμ΄μΌν¨
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(99, stock.getQuantity());
}
}
λ¨Όμ νλμ μ€λ λκ° μ¬κ³ κ°μ λ‘μ§μ νΈμΆνλ κ²½μ°λ₯Ό ν μ€νΈνλ€.
μ€ν ν΄λ³΄λ©΄ ν μ€νΈκ° μ±κ³΅νλ κ²μ λ³Ό μ μλ€.
λ€μμΌλ‘λ 100κ°μ μ€λ λκ° λμμ μ¬κ³ κ°μ λ‘μ§μ νΈμΆνλ κ²½μ°λ₯Ό ν μ€νΈνλ€.
@Test
public void λμμ_100κ°μ_μμ²() throws InterruptedException {
// when
// 100κ°μ μ°λ λ μ¬μ©(λ©ν°μ€λ λ)
int threadCount = 100;
// ExecutorService : λΉλκΈ°λ‘ μ€ννλ μμ
μ κ°λ¨νκ² μ€νν μ μλλ‘ μλ°μμ μ 곡νλ API
ExecutorService executorService = Executors.newFixedThreadPool(32);
// CountDownLatch : μμ
μ μ§νμ€μΈ λ€λ₯Έ μ€λ λκ° μμ
μ μλ£ν λκΉμ§ λκΈ°ν μ μλλ‘ λμμ£Όλ ν΄λμ€
CountDownLatch latch = new CountDownLatch(threadCount);
// 100κ°μ μμ
μμ²
for(int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
stockService.decrease(1L, 1L);
} finally {
// CountDownLatch 1 κ°μ
latch.countDown();
}
});
}
// CountDownLatchμ΄ 0μ΄ λ λκΉμ§ μ€λ λ λκΈ° - await() μ΄ν λ‘μ§μ CountDownLatchμ΄ 0μ΄ λκ³ λμ μνλλ€.
latch.await();
// then
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0, stock.getQuantity());
}
μ¬κ³ μλμ 100κ°λ‘ μΈν νκ³ 100κ°μ μ€λ λκ° ν λ²μ© μ¬κ³ κ°μ λ‘μ§μ νΈμΆνκΈ° λλ¬Έμ μ΅μ’ μ μΌλ‘ 0κ°μ μλμ΄ λ¨κ² λ κ²μ μμνλ€.
νμ§λ§ μ€ν ν΄λ³΄λ©΄ μ΅μ’ μ μΌλ‘ 95κ°μ μ¬κ³ μλμ΄ λ¨κ³ ν μ€νΈλ μ€ν¨νλ€.
λ¬Έμ λ°μ μμΈ
μ΄λ λκ° μ΄μμ μ€λ λκ° μ¬κ³ (Stock)λΌλ 곡μ λ°μ΄ν°μ λμμ μ κ·Όνκ³ λ³κ²½νλ©΄μ κ²½ν© μν(Race Condition)μ΄ λ°μνκΈ° λλ¬Έμ΄λ€. λ€μκ³Ό κ°μ΄ μμ°¨μ μΌλ‘ μ¬κ³ μ μλ(quantity)μ μ λ°μ΄νΈν κ²μ μμνλ€.
νμ§λ§ μ€μ λ‘λ Thread-1μ΄ μ¬κ³ μλμ μ‘°ν(e.g. quantity = 5)νκ³ μλμ 1λ§νΌ κ°μνκ³ λμ DBμ λ°μνκΈ° μ μ Thread-2κ° λμΌν μλμ μ¬κ³ λ₯Ό μ‘°ννκ³ 1λ§νΌ κ°μνλ€. μ΄λ Thread-1μ΄ 1λ§νΌ κ°μν μ¬κ³ μλ(e.g. quantity = 4)μ DBμ λ°μνκ³ Thread-2λ 1λ§νΌ κ°μν κ°μΌλ‘ κ°±μ νλ©΄ Thread-1μ΄ μμ
νκ³ λ°μν κ²°κ³Όλ μ¬λΌμ§λ€. (μ΄λ₯Ό Lost updateλΌ νλ€.)
κ²°κ΅ λ κ° μ΄μμ μ€λ λκ° id = 1μΈ Stockμ΄λΌλ 곡μ λ°μ΄ν°μ μμΈμ€νμ¬ λμμ λ³κ²½νλ©΄μ κ²½ν© μν(Race condition)μ΄ λ°μν κ²μ΄λ€. μ΄λ₯Ό ν΄κ²°νκΈ° μν΄μλ νλμ μ€λ λκ° μμ μ΄ μλ£λ μ΄νμ λ€λ₯Έ μ€λ λκ° λ°μ΄ν°μ μ κ·Όν μ μλλ‘ μ μ΄λ₯Ό ν΄μΌνλ€.
Application Levelμμ λ¬Έμ ν΄κ²°
λ¨Όμ Application Levelμμ λμμ± μ΄μμΈ κ²½ν© μνλ₯Ό ν΄κ²°νλ€.
μλ°μμ μ 곡νλ Synchronized ν€μλλ‘ λ¬Έμ λ₯Ό ν΄κ²°ν΄λ³Έλ€.
Synchronized
ν κ°μ μ€λ λλ§ μ κ·Όμ΄ κ°λ₯νλλ‘ μλ°μμ μ 곡νλ ν€μλμ΄λ€.
λ©μλ μ μΈλΆμ λ€μκ³Ό κ°μ΄ Synchronized ν€μλλ₯Ό μΆκ°νλ©΄ ν κ°μ μ€λ λλ§ λ©μλμ μ κ·Όμ΄ κ°λ₯νλ€.
@Service
public class StockService {
private final StockRepository stockRepository;
public StockService(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
// μ¬κ³ κ°μ λ‘μ§ κ΅¬ν
@Transactional
public synchronized void decrease(Long id, Long quantity) {
// Stock μ‘°ν
Optional<Stock> stock = stockRepository.findById(id);
// μ¬κ³ κ°μ
stock.orElseThrow(() -> new RuntimeException("ν΄λΉνλ μ¬κ³ κ° μμ΅λλ€."))
.decrease(quantity);
// κ°±μ λ κ°μ μ μ₯
stockRepository.save(stock.get());
}
}
StockServiceTestμ μμ±νλ λμμ_100κ°μ_μμ²()λ₯Ό νΈμΆνμ¬ ν μ€νΈλ₯Ό λ€μ μ€νν΄λ³Έλ€.
ν μ€νΈκ° μ±κ³΅ν μ€ μμμΌλ μ¬μ ν ν μ€νΈκ° μ€ν¨νλ κ²μ λ³Ό μ μλ€.
Synchronized μ€ν¨ μμΈ
μ΄λ StockServiceμ decrease() λ©μλμ μΆκ°ν μ€νλ§μ @Transactional μ΄λ
Έν
μ΄μ
μ λμ λ°©μ λλ¬Έμ΄λ€. μ€νλ§μ @Transactional μ΄λ
Έν
μ΄μ
μ λ©μλ νΉμ ν΄λμ€ λ 벨μ μΆκ°νλ©΄, μ€νλ§μ ν΄λΉ ν΄λμ€κ° μλ ν΄λΉ ν΄λμ€λ₯Ό κ°μΈκ³ μλ proxy κ°μ²΄λ₯Ό λΉμΌλ‘ λ±λ‘νκ³ μμ‘΄κ΄κ³ μ£Όμ
μ μ¬μ©νλ€.
μ¦, λ€μκ³Ό κ°μ΄ StockServiceλ₯Ό κ°μΈκ³ μλ TransactionStockService ν΄λμ€λ₯Ό μ€νλ§μ΄ λΉμΌλ‘ λ±λ‘νλ κ²μ΄λ€. μ€νλ§μ @Transactional μ΄λ
Έν
μ΄μ
μ μλ μ½λμ κ°μ΄ νΈλμμ
μ μμνκ³ νκ² ν΄λμ€λ₯Ό νΈμΆνκ³ λμ λ‘μ§μ΄ μλ£λλ©΄ λ³κ²½μ¬νμ 컀λ°(νΉμ λ‘€λ°±)νκ³ νΈλμμ
μ μ’
λ£νλ λ°©μμΌλ‘ λμνλ€.
package com.example.stock.service;
public class TransactionStockService {
private StockService stockService;
public TransactionStockService(StockService stockService) {
this.stockService = stockService;
}
public void decrease(Long id, Long quantity) {
// νΈλμμ
μμ
startTransaction();
// νκ² ν΄λμ€ νΈμΆ
stockService.decrease(id, quantity);
// νΈλμμ
μ’
λ£
endTransaction();
}
private void startTransaction() {
System.out.println("Transaction started");
}
private void endTransaction() {
System.out.println("Commit");
}
}
κ·Έλ¦ΌμΌλ‘ λνλ΄λ©΄ λ€μκ³Ό κ°λ€.
νμ§λ§ μ΄λ¬ν @Transactional μ΄λ Έν μ΄μ μ λμ λ°©μ λλ¬Έμ μλμ λ€λ₯΄κ² λμνλ€.
decrease() λ©μλμ @Transactional μ΄λ Έν μ΄μ μ λκ²λλ©΄ StockRepositoryμ save() λ©μλλ₯Ό νΈμΆνλ μμ μ κ°±μ λ κ°μ΄ DBμ λ°μ(컀λ°)νλ κ²μ΄ μλλΌ StockServiceμ λͺ¨λ λ‘μ§μ μννκ³ λμ StockServiceμ proxy κ°μ²΄κ° λ³κ²½μ¬νμ λ°μ(컀λ°)νλ€.
λ€μ λ§ν΄ StockServiceμ decrease λ©μλλ ν λ²μ ν μ€λ λλ§ μ κ·ΌνκΈ° μν΄ synchronized ν€μλλ₯Ό λμμ΄λ, @Transactionalλ‘ μΈν΄ νλ‘μ κ°μ²΄μλ μ¬λ¬ μ€λ λκ° λμμ μ κ·Όμ΄ κ°λ₯νλ€. κ·Έλμ proxy κ°μ²΄κ° λ³κ²½μ¬νμ λ°μ(컀λ°)νκΈ° μ μ λ€λ₯Έ μ€λ λμμ μ¬κ³ μλμ μ‘°ννλ©΄ μ΄μ μ μ¬κ³ μλμ μ‘°ννκ² λκ³ κ²°κ΅ μ¬μ ν κ²½ν© μνκ° λ°μνλ κ²μ΄λ€.
@Transactional λμμ리
κ·Έλ¦ΌμΌλ‘ μ’ λ μμΈν μμ보μ.
@Transactionμ΄ μ μ©λ ν΄λμ€λ CGLIBμ μν΄μ λ°νμμ ν΄λΉ ν΄λμ€ κΈ°λ° νλ‘μκ° μμ±λλ€
κ·Έλ¦¬κ³ @Transactional λ‘μ§μΌλ‘ μ§μ νκΈ° μ /νμμ Transaction Begin & Commit/Rollbackμ΄ μ§νλλ κ²μ΄λ€
- μ΄λ κ² @Transactionalμ΄ κ±Έλ €μλ λΉμ¦λμ€ λ‘μ§μ synchronized ν€μλλ₯Ό λΆμ΄κ² λλ€λ©΄ λ€μκ³Ό κ°μ΄ λμνλ€
- ν΄λΉ λΉμ¦λμ€ λ‘μ§μ synchronizedκ° κ±Έλ €μμΌλ ν΄λΉ λ‘μ§μΌλ‘ μ§μ ν λ Monitor Lockμ κ°μ§κ³ μ§μ νκ² λλκ²μ΄λ€
- κ·Έλ¬λ©΄ Thread1μ μ μΈν λλ¨Έμ§ μ°λ λλ€μ λΉμ¦λμ€ λ‘μ§μ μ κ·Όνμ§ λͺ»νκ³ Lockμ μ»κΈ° μν΄μ λκΈ°νλ€
- μ¬κΈ°μ Thread1μ΄ λΉμ¦λμ€ λ‘μ§μ λλ΄κ³ 컀λ°/λ‘€λ°± μμ μΌλ‘ λμ νλ€κ³ κ°μ νμ
- μ΄ μμ μ Thread2κ° μ§μ νκ² λλ©΄ μμ§ Thread1μ λ‘μ§μ΄ commitλκΈ° μ μ΄λ―λ‘ DBμ μ‘΄μ¬νλ stockμ μλμ μ¬μ ν 100μ΄λ€
- κ·Έμ λ°λΌμ Thread2λ stockμ μλμ 100μΌλ‘ λ°κ² λκ³ κ·Έμ λ°λ₯Έ λ‘μ§μ΄ μ§νλλ€
μ΄λ¬ν μ΄μ λ‘ μΈν΄μ @Transactional + synchronizedλ Springμ Proxy λ©μ»€λμ¦μΌλ‘ μΈν΄ λμμ± λ¬Έμ λ₯Ό ν΄κ²°νμ§ λͺ»νλ κ²μ΄λ€
λ¬Όλ‘ @Transactional μ΄λ Έν μ΄μ μ ν΄μ νκ³ μ€ννλ©΄ ν μ€νΈ μΌμ΄μ€κ° μ±κ³΅νλ κ²μ λ³Ό μ μλ€.
Synchronizedμ @Transactional μ¬μ© μ΄μ
κ·Έλ λ€κ³ λμμ± μ΄μλ₯Ό ν΄κ²°νκΈ° μν΄ @Transactional μ΄λ
Έν
μ΄μ
μ μ¬μ©νμ§ λ§κ³ synchronized ν€μλλ₯Ό λμ΄μΌν κΉ?
μ€νλ§μ @Transactional μ΄λ
Έν
μ΄μ
μ λ°μ΄ν° κ΄λ¦¬ κ΄μ μμ κ΅μ₯ν νΈμμ±μ κ°λ°μμκ² μ 곡νλ€.
λ§μΌ μ΄ μ΄λ
Έν
μ΄μ
μ μ¬μ©νμ§ μλλ€λ©΄ κ°λ°μλ λΉμ¦λμ€ λ‘μ§μ μμ±ν λλ§λ€ λ§€λ² DB 컀λ₯μ
μ μ‘°ννκ³ μ»€λ°νκ±°λ λ‘€λ°±νλ λ‘μ§μ μμ±ν΄μ£Όμ΄μΌν κ²μ΄λ€. λν μ€νλ§μ νΈλμμ
맀λμ λ λ€μν λ°μ΄ν° μ κ·Ό κΈ°μ μ λν μΆμνλ νΈλμμ
κ΄λ¦¬ λ°©μμ μ 곡νλλ° μ΄λ¬ν μ΄μ λ μ»μ μ μλ€.
λν μ€νλ§μ @Transactional μ΄λ
Έν
μ΄μ
μ μ²΄ν¬ μμΈμ μΈμ²΄ν¬ μμΈ(λ°νμ μμΈ) λ³λ‘ λ€λ₯΄κ² λμνλ©° μμ μ μΈ νλ‘κ·Έλ¨ κ°λ°μλ λ§μ κΈ°μ¬λ₯Ό νλ€.
μ컨λ μμμΉ λͺ»ν λ°νμ μμΈ(e.g. NullPointerException, IllegalArgumentException)κ° λ°μνλ©΄ νΈλμμ
μ λ‘€λ°±νμ¬ λ³κ²½μ¬νμ DBμ λ°μνμ§ μλλ€. λ°λ©΄ λΉμ¦λμ€ μμΈμ μλμ μΌλ‘ λμ§λ μμΈλ₯Ό ꡬννλλ° μ£Όλ‘ μ°μ΄λ μ²΄ν¬ μμΈκ° λ°μνλ©΄ νΈλμμ
μ 컀λ°νμ¬ μ§ν μνλ₯Ό DBμ λ°μνλ μμ΄λ€. (λ¬Όλ‘ @Transactionalμ rollbackFor μμ±μ μ¬μ©νμ¬ νΉμ μ²΄ν¬ μμΈμ λν΄μλ λ‘€λ°±μ μ§μ ν μ μλ€.) λ§μΌ @Transactional μ΄λ
Έν
μ΄μ
μ μ¬μ©νμ§ μλλ€λ©΄ μ΄μ²λΌ μμΈ κ³μΈ΅ λ³λ‘ μ μ ν μ²λ¦¬ λ°©μμ κ°λ°μκ° λͺ¨λ μμ±ν΄μ£Όμ΄μΌν κ²μ΄λ€.
λΆμ° νκ²½μμμ synchronized νκ³
μ΄ μΈμλ μλ°μ Synchronized ν€μλκ° μ 곡νλ λκΈ°ν μμ μ νλμ νλ‘μΈμ€ μμμλ§ λ³΄μ₯μ΄ λλ€λ μΉλͺ μ μΈ λ¨μ μ΄ μλ€. μ¦ ν λμ μλ²μμ λ©ν°μ°λ λλ‘ μμ νλ κ²½μ°μλ, 곡μ μμμ λν μ κ·Όμ ν λμ μλ²μμμλ§ μ΄λ£¨μ΄μ§κΈ° λλ¬Έμ μμ ν μ κ·Όμ 보μ₯νλ€. νμ§λ§ μλ²κ° μ¬λ¬λμΈ κ²½μ° μ¬λ¬ μλ²μμ 곡μ μμμ μ κ·Όν μ μμΌλ―λ‘ μ€λ λ μμ μ±μ΄ 보μ₯μ΄ λμ§ μλλ€.
μ€λ¬΄μμ μΌλ°μ μΌλ‘(κ°λ¨ν μμ€ν
μΌμ§λΌλ) μ΄μ μλ²λ λ³΄ν΅ ν λλ§ λμ§ μλλ€.
μ¬λ¬ λμ μ₯λΉμ μλ² μΈμ€ν΄μ€λ₯Ό λλλ° κ²°κ΅ μ¬λ¬λμ μλ²κ° λμμ 곡μ μμμ μ κ·Όνκ²λλ©΄ κ²°κ΅ μ¬μ ν Race Conditionμ΄ λ°μνλ κ²μ΄λ€. λ°λΌμ μ€λ¬΄μμ Synchronizedλ₯Ό κ±°μ μ¬μ©νμ§ μλλ€.
κ²°κ΅ λμμ± μ΄μλ₯Ό ν΄κ²°νλ €λ©΄ λ³λμ λκΈ°ν λ©μ»€λμ¦μ΄ νμνλ€.
λ€μ κΈμμλ λ°μ΄ν°λ² μ΄μ€μμ μ 곡νλ Lockμ νμ©νμ¬ λ¬Έμ λ₯Ό ν΄κ²°ν΄λ³Έλ€.
μ°Έκ³
'Spring > λ½' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
λμμ± μ΄μ ν΄κ²°λ°©λ² (3/3) - λ λμ€ λΆμ° λ½(Lock)μΌλ‘ ν΄κ²°νκΈ° (0) | 2024.09.16 |
---|---|
λμμ± μ΄μ ν΄κ²°λ°©λ² (2/3) - λ°μ΄ν°λ² μ΄μ€ λ½(Lock)μΌλ‘ ν΄κ²°νκΈ° (0) | 2024.09.07 |