# ๐ ์ฐจ๊ทผ์ฐจ๊ทผ ๋์์ฑ ์ด์ ํด๊ฒฐํ๊ธฐ (3)
๐ ์ฐจ๊ทผ์ฐจ๊ทผ ๋์์ฑ ์ด์ ํด๊ฒฐํ๊ธฐ (1) - synchronized (opens new window)
๐ ์ฐจ๊ทผ์ฐจ๊ทผ ๋์์ฑ ์ด์ ํด๊ฒฐํ๊ธฐ (2) - Pessimistic Lock (opens new window)
๐ ๐ ์ฐจ๊ทผ์ฐจ๊ทผ ๋์์ฑ ์ด์ ํด๊ฒฐํ๊ธฐ (3) - Optimistic Lock (opens new window)
๐ ์ฐจ๊ทผ์ฐจ๊ทผ ๋์์ฑ ์ด์ ํด๊ฒฐํ๊ธฐ (4) - Named Lock (opens new window)
์์ฑ์ ์ฌ์ฉ๋ ์์ ์ฝ๋๋ concurrency-optimistic-lock (opens new window)์์ ํ์ธํด๋ณผ ์ ์๋ค.
# ๋๊ด์ ๋ฝ
๋๊ด์ ๋ฝ์ด๋ ํธ๋์ญ์
์ถฉ๋์ด ๋ฐ์ํ์ง ์๋๋ค๊ณ ๋๊ด์ ์ผ๋ก ๊ฐ์ ํ๋ค. ์ถ๊ฐ์ ์ธ version
์ ๊ธฐ๋ฐ์ผ๋ก ์ถฉ๋ํ ๊ฒฝ์ฐ ๋กค๋ฐฑ์ ์งํํ๋ค. ๊ฐ์ฅ ํฐ ์ฅ์ ์ ์ถฉ๋์ด ์ผ์ด๋์ง ์๋๋ค๊ณ ๊ฐ์ ํ๊ธฐ ๋๋ฌธ์ ๋์ ์ฒ๋ฆฌ์ ๋ํ
์ด์ ์ด ๋ง๋ค.
# @Version
๋๊ด์ ๋ฝ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์ง๋ง ๋ํ์ ์ผ๋ก๋ @Version
์ ๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด๋ค. @Version
์ ๋๊ด์ ๋ฝ ๊ฐ์ผ๋ก ์ฌ์ฉ๋๋ ์ํฐํฐ ํด๋์ค์ ๋ฒ์ ํ๋ ๋๋ ์์ฑ์ ์ง์ ํ๋ค. ์ด
๋ฒ์ ์ ๋ณํฉ ์์
์ ์ํํ ๋ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํ๊ณ ๋๊ด์ ๋์์ฑ ์ ์ด๋ฅผ ์ํด ์ฌ์ฉ ๋๋ค. @Version
์ ํด๋์ค๋น ํ๋์ ์์ฑ ๋๋ ํ๋๋ง ์ฌ์ฉํด์ผ ํ๋ค. ๋ ์ด์์ ๋ฒ์ ๋๋ ์์ฑ ํ๋๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
@Version
์ ์ํฐํฐ ํด๋์ค์ ๊ธฐ๋ณธ ํ
์ด๋ธ์ ๋งคํ๋์ด์ผ ํ๋ค. ๊ธฐ๋ณธ ํ
์ด๋ธ์ด ์๋ ๋ค๋ฅธ ํ
์ด๋ธ์ ๋งคํํ ์ ์๋ค. @Version
์ด ์ง์๋๋ type์ int
, Integer
, short
, long
, Long
, java.sql.Timestamp
์ด๋ค. @Version
์ด ์ฌ์ฉ๋ ์ํฐํฐ๋ ์์
๋ ๋ ์๋์ผ๋ก ๋ฒ์ ์ด ์ฆ๊ฐํ๋ฉฐ ์์ ํ ๋ ์กฐํ ์์ ๊ณผ ๋ฒ์ ์ด ๋ค๋ฅธ ๊ฒฝ์ฐ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ฌํ ๋ฒ์ ์ ์ํฐํฐ ์์ ์์ ์ ์ต์ ํ๋๋ฉฐ JPA์ ์ํด ์๋์ ์ผ๋ก ๊ด๋ฆฌ๋๋ค.
update product
set name = ?,
quantity = ?,
version = ?
where id = ?
and version = ?
# ๋๊ด์ ๋ฝ ์ ์ฉ
์์ ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ์ด์ ์ ์์ฑํ Product ์ํฐํฐ๋ฅผ ํ์ฉํ๋ค. ๋จผ์ @Version
์ด๋ผ๋ ์ ๋
ธํ
์ด์
์ ํ์ฉํ์ฌ version ๊ด๋ จ ์นผ๋ผ์ ์ถ๊ฐํ๋ค.
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "quantity")
private Long quantity;
@Version
private Long version;
// ...
}
version
์ด๋ผ๋ ์นผ๋ผ์ด ์ถ๊ฐ์ ์ผ๋ก ์์ฑ๋์ด ํ
์ด๋ธ์ด ์์ฑ๋๋ค. ์ด๋ฌํ Version ์ ๋ณด๋ฅผ ์ฌ์ฉํ๋ฉด ์ต์ด์ ์ ์ฉ๋ ์ปค๋ฐ๋ง ๋ฐ์๋๊ณ ๋๋จธ์ง๋ ๋ฌด์๋๋ค.
create table product
(
id bigint not null auto_increment,
name varchar(255),
quantity bigint,
version bigint,
primary key (id)
)
์๋๋ ๊ตฌ๋งค์ ๊ด๋ จ๋ ๋น์ฆ๋์ค ๋ก์ง์ด ๋ด๊ธด ProductService
์ด๋ค. ์ด์ ์ฝ๋์ ๋๋ถ๋ถ ์ ์ฌํ๋ค.
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(final ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Transactional
public void purchase(final Long id, final Long quantity) {
var foundProduct = getProduct(id);
foundProduct.decrease(quantity);
}
private Product getProduct(final Long id) {
return productRepository.findByIdWithOptimisticLock(id)
.orElseThrow(NoSuchElementException::new);
}
}
์ด์ ๋์์ฑ ํ ์คํธ๋ฅผ ์งํํ๋ค.
@SpringBootTest
@DisplayNameGeneration(ReplaceUnderscores.class)
class ProductServiceTest {
private final ProductRepository productRepository;
private final ProductService productService;
@Autowired
ProductServiceTest(final ProductRepository productRepository,
final ProductService productService) {
this.productRepository = productRepository;
this.productService = productService;
}
@Test
void ๋์์_100๊ฐ์_์ํ์_๊ตฌ๋งคํ๋ค() throws InterruptedException {
var product = productRepository.save(new Product("์นํจ", 100L));
var executorService = Executors.newFixedThreadPool(10);
var countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
executorService.submit(() -> process(product, countDownLatch));
}
countDownLatch.await();
var actual = productRepository.findById(product.getId()).orElseThrow();
assertThat(actual.getQuantity()).isEqualTo(0L);
}
}
์ํ๊น๊ฒ๋ ๋๊ด์ ๋ฝ์ WHERE ์กฐ๊ฑด์ ์ํด ์์ ํ ๋์์ด ์๋ ๊ฒฝ์ฐ ๋ฒ์ ์ด ์ฆ๊ฐ ํ๋ค๊ณ ํ๋จํ์ฌ ์์ธ๋ฅผ ๋์ง๋ค.
Spring Data JPA๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ JPA์ ๋ํ ๊ตฌํ์ฒด๋ ๊ธฐ๋ณธ์ ์ผ๋ก hibernate๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค. ์ด ์์ธ๋ ๊ธฐ๋ณธ์ ์ผ๋ก hibernate์์ ๋์ง๋ StaleStateException
์์ธ๋ฅผ
Spring์ HibernateJpaDialect
์ convertHibernateAccessException()
๋ฉ์๋๋ฅผ ํตํด org.springframework.dao
๊ณ์ธต์์ ์ ์ ํ ์์ธ(
DataAccessException ํ์
)๋ก ๋ณํ๋๋ค.
package org.springframework.orm.jpa.vendor;
// ...
public class HibernateJpaDialect extends DefaultJpaDialect {
// ...
protected DataAccessException convertHibernateAccessException(HibernateException ex) {
// ...
if (ex instanceof StaleStateException) {
return new ObjectOptimisticLockingFailureException(ex.getMessage(), ex);
}
// ...
}
}
๊ฒฐ๊ตญ ๋๊ด์ ๋ฝ์ ์คํจ ์ ์์ธ๋ฅผ ๋์ง๊ธฐ ๋๋ฌธ์ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ฌ์๋ ํ๋ ๋ก์ง
์ด ํ์ํ ์ ์๋ค. ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ ์ถฉ๋์ด ์ผ์ด๋ ์์ ์ ๋์ง๋ ์์ธ๋ฅผ ์บ์นํ์ฌ ๋ค์ ๋ก์ง ์ฒ๋ฆฌ๋ฅผ ์ํ ์์ฒญ์ ๋ณด๋ด๋ ๊ฒ์ด๋ค.
@Component
public class OptimisticLockProductFacade {
private final ProductService productService;
public OptimisticLockProductFacade(final ProductService productService) {
this.productService = productService;
}
public void purchase(final Long id, final Long quantity) throws InterruptedException {
while (true) {
try {
productService.purchase(id, quantity);
break;
} catch (final ObjectOptimisticLockingFailureException e) {
Thread.sleep(100);
}
}
}
}
๊ตฌ์กฐ๋ ๋งค์ฐ ๊ฐ๋จํ๋ค. ๋ฌดํ ๋ฃจํ๋ฅผ ๋๋ฉฐ ๊ณ์ ๊ตฌ๋งค๋ฅผ ์งํํ๋ฉด ๋๋ค. ๋๊ด์ ๋ฝ ๊ด๋ จ ์์ธ๊ฐ ๋ฐ์ํ์ ๋๋ ์ ๊น์ ์ฌ๋ ์๊ฐ ์ดํ ๋ค์ ์์ฒญ์ ์งํํ๋ค. ๋ค์ ํ ์คํธ๋ฅผ ์งํํด๋ณด์.
์ฑ๊ณต์ ์ผ๋ก ํต๊ณผํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
# ์ถํ ์์๋ณผ ๊ฒ
public interface ProductRepository extends JpaRepository<Product, Long> {
@Lock(LockModeType.OPTIMISTIC)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Optional<Product> findByIdWithOptimisticLock(final Long id);
}
์์ ๊ฐ์ด @Lock(LockModeType.OPTIMISTIC)
์ ์ฌ์ฉํ์ฌ ์กฐํ ์ฟผ๋ฆฌ์ ๋๊ด์ ๋ฝ์ ๋ช
์ํ๋ฉด ์๋์ ๊ฐ์ด version์ ๊ฒ์ฆํ๊ธฐ ์ํ SELECT ์ฟผ๋ฆฌ๊ฐ ์ถ๊ฐ๋๋ค.
select version as version_
from product
where id = ?
์ด ์ฟผ๋ฆฌ์ ์ฉ๋๋ ๋ฌด์์ผ๊น? ๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด ๋ฒ์ ์ด ์ง์ ๋ ๊ฐ์ฒด์ ๋ํด @Lock(LockModeType.OPTIMISTIC)
์ ํ์ ์ ๊ธ์ ํธ์ถํ๋ ๊ฒฝ์ฐ Drity Read
์ Non-repeatable read
๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์กฐ์ฌํด์ผ ํ๋ค๊ณ ์ธ๊ธ๋์ด ์๋ค. ์ด ๋ถ๋ถ์ ๋ํด ์์ง ๋ช
ํํ๊ธฐ ์ดํด๊ฐ ๋์ง ์์ ๋๋ ทํ ์ ์ฉ ์ฌ๋ก๊ฐ ๋ ์ค๋ฅด์ง ์๋๋ค.
# ์ ๋ฆฌ
๋๊ด์ ๋ฝ์ @Version
์ด๋ผ๋ ์ ๋
ธํ
์ด์
์ผ๋ก ์์ฝ๊ฒ ์ ์ฉ์ด ๊ฐ๋ฅํ๋ค. ๋ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ๋ฝ์ ๊ฑฐ๋ ๊ฒ์ด ์๋๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ์์ ์ด์ ์ด ๋ง๋ค.
ํ์ง๋ง UPDATE๊ฐ ์คํจ ํ์ ๋ ์๊ตฌ์ฌํญ์ ๋ง๋ ์ฌ์๋ ๋ก์ง์ ์ง์ ์์ฑํด์ผ ํ๋ค. ๋ง์ฝ ์ถฉ๋์ด ๋น๋ฒํ๊ฒ ์ผ์ด๋๋ ๊ฒฝ์ฐ์๋ ๋๊ด์ ๋ฝ์ ์ข์ ํด๊ฒฐ์ฑ ์ด ์๋ ์ ์๋ค.
๋ค์ ์๊ฐ์๋ ์ถ๊ฐ์ ์ธ DataSource
๋ฅผ ํ์ฉํ์ฌ ๋ณ๋์ ๊ณต๊ฐ์ ๋ฝ์ ๊ด๋ฆฌํ๋ Named Lock
์ ํตํด ๋์์ฑ ์ด์๋ฅผ ํด๊ฒฐํด๋ณด๋ ค ํ๋ค.
# References.
์ฌ๊ณ ์์คํ ์ผ๋ก ์์๋ณด๋ ๋์์ฑ์ด์ ํด๊ฒฐ๋ฐฉ๋ฒ (opens new window)