개요
DB에 저장되는 데이터의 일관성을 유지하고 충돌을 방지할 수 있는 방법인 DB Lock에 대해 정리할 것이다.
DB Lock?
DB Lock은다수의 사용자나 프로세스가 데이터베이스의 동일한 리소스(테이블, 행 등)에 동시에 접근하거나 수정하려고 할 때 발생할 수 있는 데이터 무결성 문제를 방지하기 위해 사용되는 메커니즘.
DB Lock 주요 개념
- Lock의 필요성:
- 다중 사용자 환경에서 데이터베이스가 동일한 데이터를 동시에 읽거나 쓸 경우, 데이터 정합성 문제나 경합 조건(Race Condition)이 발생할 수 있다.
- Lock은 이런 문제를 방지하여 데이터 무결성을 보장한다.
- Lock의 수준:
- 테이블 수준 락(Table-Level Lock): 테이블 전체를 잠그는 방식으로, 간단하지만 동시성이 낮아진다.
- 행 수준 락(Row-Level Lock): 특정 행만 잠그는 방식으로 동시성이 높아진다.
- 페이지 수준 락(Page-Level Lock): 데이터가 저장된 페이지(블록 단위)를 잠그는 방식으로, 테이블과 행 수준 락의 중간 수준이다.
- Lock의 유형:
- 공유 락(Shared Lock, S-Lock): 데이터를 읽을 때 설정되며, 여러 트랜잭션이 동시에 공유 락을 설정할 수 있다. 하지만 데이터 수정은 불가능하다.
- 베타 락(Exclusive Lock, X-Lock): 데이터를 수정할 때 설정되며, 한 트랜잭션만 베타 락을 설정할 수 있다. 다른 트랜잭션은 접근할 수 없다.
- 의도 락(Intent Lock): 계층적 리소스(예: 테이블과 행)에서의 잠금 계획을 명시하여 상충을 피하도록 돕는다.
DB Lock 사용 시 발생할 수 있는 문제
- 데드락(Deadlock):
- 두 개 이상의 트랜잭션이 서로가 필요한 리소스를 잠그고 있어 무한 대기 상태에 빠지는 상황.
- 해결 방법:
- 타임아웃 설정.
- 교착 상태 탐지 알고리즘 사용.
- 트랜잭션 순서를 정해 데드락 방지.
- 락 경합(Lock Contention):
- 다수의 트랜잭션이 동일한 리소스를 동시에 잠그려 할 때 성능 저하가 발생.
- 과도한 락 사용:
- 불필요하게 넓은 범위(예: 테이블 전체)로 락을 설정하면 동시성이 낮아지고 성능 저하가 발생.
DB Lock을 최적화하는 방법
- 트랜잭션의 크기를 줄이기:
- 락이 오래 유지되지 않도록 트랜잭션 내 작업을 최소화.
- 적절한 인덱스 설정:
- 행 단위 락에서 불필요한 테이블 스캔 방지.
- 락 타임아웃 설정:
- 일정 시간 이상 대기하지 않도록 타임아웃 설정.
- 낙관적 동시성 제어(Optimistic Concurrency Control):
- 락 대신 버전 번호를 사용해 충돌 감지 후 재시도
DB Lock 실습
- 비관적 락, 낙관적 락, 데드 락
설정
application.yml
spring:
application:
name: locking
jpa:
properties:
hibernate:
format_sql: true
use_sql_comments: true
logging:
level:
org:
hibernate:
SQL: debug
orm:
jdbc:
bind: TRACE
비관적 락(Pessimistic Lock)
Item
@Data
@Entity
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer quantity;
}
ItemRepository
public interface ItemRepository extends JpaRepository<Item, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select i from Item i where i.id = :id")
Item findByIdWithLock(Long id);
}
ItemService
@Service
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
@Transactional
public void updateItemQuantity(Long itemId, Integer newQuantity) {
Item item = itemRepository.findByIdWithLock(itemId);
item.setQuantity(newQuantity);
itemRepository.save(item);
}
@Transactional
public Item findItemById(Long itemId) {
return itemRepository.findById(itemId).orElse(null);
}
}
ItemServiceTest
@SpringBootTest
public class ItemServiceTest {
private static final Logger logger = LoggerFactory.getLogger(ItemServiceTest.class);
@Autowired
private ItemService itemService;
@Autowired
private ItemRepository itemRepository;
@Test
public void testPessimisticLocking() throws InterruptedException {
logger.info("초기 아이템 데이터 설정");
Item item = new Item();
item.setName("Item 1");
item.setQuantity(10);
itemRepository.save(item);
Thread thread1 = new Thread(() -> {
logger.info("스레드 1: 아이템 수량 업데이트 시도");
itemService.updateItemQuantity(item.getId(), 20);
logger.info("스레드 1: 아이템 수량 업데이트 완료");
});
Thread thread2 = new Thread(() -> {
logger.info("스레드 2: 아이템 수량 업데이트 시도");
itemService.updateItemQuantity(item.getId(), 30);
logger.info("스레드 2: 아이템 수량 업데이트 완료");
});
thread2.start();
thread1.start();
thread1.join();
thread2.join();
Item updateItem = itemService.findItemById(item.getId());
logger.info("최종 아이템 수량 : {}", updateItem.getQuantity());
}
}
테스트 결과
2024-12-24T11:43:20.771+09:00 INFO 21952 --- [locking] [ Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-12-24T11:43:21.294+09:00 INFO 21952 --- [locking] [ Test worker] o.s.d.j.r.query.QueryEnhancerFactory : Hibernate is in classpath; If applicable, HQL parser will be used.
2024-12-24T11:43:22.520+09:00 WARN 21952 --- [locking] [ Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-12-24T11:43:23.394+09:00 INFO 21952 --- [locking] [ Test worker] c.example.locking.item.ItemServiceTest : Started ItemServiceTest in 8.153 seconds (process running for 9.972)
2024-12-24T11:43:24.049+09:00 INFO 21952 --- [locking] [ Test worker] c.example.locking.item.ItemServiceTest : 초기 아이템 데이터 설정
2024-12-24T11:43:24.108+09:00 DEBUG 21952 --- [locking] [ Test worker] org.hibernate.SQL :
/* insert for
com.example.locking.item.Item */insert
into
item (name, quantity, id)
values
(?, ?, default)
2024-12-24T11:43:24.124+09:00 TRACE 21952 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [Item 1]
2024-12-24T11:43:24.125+09:00 TRACE 21952 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (2:INTEGER) <- [10]
2024-12-24T11:43:24.193+09:00 INFO 21952 --- [locking] [ Thread-4] c.example.locking.item.ItemServiceTest : 스레드 1: 아이템 수량 업데이트 시도
2024-12-24T11:43:24.193+09:00 INFO 21952 --- [locking] [ Thread-5] c.example.locking.item.ItemServiceTest : 스레드 2: 아이템 수량 업데이트 시도
2024-12-24T11:43:24.290+09:00 DEBUG 21952 --- [locking] [ Thread-4] org.hibernate.SQL :
/* select
i
from
Item i
where
i.id = :id */ select
i1_0.id,
i1_0.name,
i1_0.quantity
from
item i1_0
where
i1_0.id=? for update
2024-12-24T11:43:24.290+09:00 DEBUG 21952 --- [locking] [ Thread-5] org.hibernate.SQL :
/* select
i
from
Item i
where
i.id = :id */ select
i1_0.id,
i1_0.name,
i1_0.quantity
from
item i1_0
where
i1_0.id=? for update
2024-12-24T11:43:24.293+09:00 TRACE 21952 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
2024-12-24T11:43:24.293+09:00 TRACE 21952 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
2024-12-24T11:43:24.325+09:00 DEBUG 21952 --- [locking] [ Thread-4] org.hibernate.SQL :
/* update
for com.example.locking.item.Item */update item
set
name=?,
quantity=?
where
id=?
2024-12-24T11:43:24.326+09:00 TRACE 21952 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [Item 1]
2024-12-24T11:43:24.327+09:00 TRACE 21952 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (2:INTEGER) <- [20]
2024-12-24T11:43:24.328+09:00 TRACE 21952 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (3:BIGINT) <- [1]
2024-12-24T11:43:24.333+09:00 INFO 21952 --- [locking] [ Thread-4] c.example.locking.item.ItemServiceTest : 스레드 1: 아이템 수량 업데이트 완료
2024-12-24T11:43:24.334+09:00 DEBUG 21952 --- [locking] [ Thread-5] org.hibernate.SQL :
/* update
for com.example.locking.item.Item */update item
set
name=?,
quantity=?
where
id=?
2024-12-24T11:43:24.335+09:00 TRACE 21952 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [Item 1]
2024-12-24T11:43:24.335+09:00 TRACE 21952 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (2:INTEGER) <- [30]
2024-12-24T11:43:24.335+09:00 TRACE 21952 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (3:BIGINT) <- [1]
2024-12-24T11:43:24.336+09:00 INFO 21952 --- [locking] [ Thread-5] c.example.locking.item.ItemServiceTest : 스레드 2: 아이템 수량 업데이트 완료
2024-12-24T11:43:24.353+09:00 DEBUG 21952 --- [locking] [ Test worker] org.hibernate.SQL :
select
i1_0.id,
i1_0.name,
i1_0.quantity
from
item i1_0
where
i1_0.id=?
2024-12-24T11:43:24.354+09:00 TRACE 21952 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
2024-12-24T11:43:24.356+09:00 INFO 21952 --- [locking] [ Test worker] c.example.locking.item.ItemServiceTest : 최종 아이템 수량 : 30
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2024-12-24T11:43:24.394+09:00 INFO 21952 --- [locking] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-12-24T11:43:24.395+09:00 DEBUG 21952 --- [locking] [ionShutdownHook] org.hibernate.SQL :
drop table if exists item cascade
2024-12-24T11:43:24.399+09:00 INFO 21952 --- [locking] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2024-12-24T11:43:24.402+09:00 INFO 21952 --- [locking] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
> Task :test
BUILD SUCCESSFUL in 27s
4 actionable tasks: 4 executed
AM 11:43:24: Execution finished ':test --tests "com.example.locking.item.ItemServiceTest"'.
- 락이 걸리고 1개의 작업이 완료되고 다음 작업이 실행되는 것을 확인할 수 있다.
낙관적 락(Optimistic Lock)
Product
@Data
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
@Version
private Integer version;
}
ProductRepository
public interface ProductRepository extends JpaRepository<Product, Long> {
}
ProductService
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
@Transactional
public void updateProductPrice(Long productId, Double newPrice) {
try {
Product product = productRepository
.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
product.setPrice(newPrice);
productRepository.save(product);
} catch (ObjectOptimisticLockingFailureException e) {
System.err.println("낙관적 락 충돌 발생!!!!!");
}
}
}
ProductServiceTest
@SpringBootTest
public class ProductServiceTest {
@Autowired
private ProductService productService;
@Autowired
private ProductRepository productRepository;
@Test
public void testOptimisticLocking() throws InterruptedException {
Product product = new Product();
product.setName("Product 1");
product.setPrice(100.0);
productRepository.save(product);
Thread thread1 = new Thread(() -> {
productService.updateProductPrice(product.getId(), 200.0);
});
Thread thread2 = new Thread(() -> {
assertThrows(OptimisticLockingFailureException.class, () -> {
productService.updateProductPrice(product.getId(), 300.0);
});
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
테스트 결과
2024-12-24T12:14:03.697+09:00 INFO 8388 --- [locking] [ Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-12-24T12:14:04.252+09:00 INFO 8388 --- [locking] [ Test worker] o.s.d.j.r.query.QueryEnhancerFactory : Hibernate is in classpath; If applicable, HQL parser will be used.
2024-12-24T12:14:05.536+09:00 WARN 8388 --- [locking] [ Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-12-24T12:14:06.311+09:00 INFO 8388 --- [locking] [ Test worker] c.e.locking.product.ProductServiceTest : Started ProductServiceTest in 7.939 seconds (process running for 9.895)
2024-12-24T12:14:06.994+09:00 DEBUG 8388 --- [locking] [ Test worker] org.hibernate.SQL :
/* insert for
com.example.locking.product.Product */insert
into
product (name, price, version, id)
values
(?, ?, ?, default)
2024-12-24T12:14:07.003+09:00 TRACE 8388 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [Product 1]
2024-12-24T12:14:07.004+09:00 TRACE 8388 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (2:DOUBLE) <- [100.0]
2024-12-24T12:14:07.004+09:00 TRACE 8388 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (3:INTEGER) <- [0]
2024-12-24T12:14:07.095+09:00 DEBUG 8388 --- [locking] [ Thread-4] org.hibernate.SQL :
select
p1_0.id,
p1_0.name,
p1_0.price,
p1_0.version
from
product p1_0
where
p1_0.id=?
2024-12-24T12:14:07.095+09:00 DEBUG 8388 --- [locking] [ Thread-5] org.hibernate.SQL :
select
p1_0.id,
p1_0.name,
p1_0.price,
p1_0.version
from
product p1_0
where
p1_0.id=?
2024-12-24T12:14:07.097+09:00 TRACE 8388 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
2024-12-24T12:14:07.098+09:00 TRACE 8388 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
2024-12-24T12:14:07.127+09:00 DEBUG 8388 --- [locking] [ Thread-4] org.hibernate.SQL :
/* update
for com.example.locking.product.Product */update product
set
name=?,
price=?,
version=?
where
id=?
and version=?
2024-12-24T12:14:07.127+09:00 DEBUG 8388 --- [locking] [ Thread-5] org.hibernate.SQL :
/* update
for com.example.locking.product.Product */update product
set
name=?,
price=?,
version=?
where
id=?
and version=?
2024-12-24T12:14:07.129+09:00 TRACE 8388 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [Product 1]
2024-12-24T12:14:07.129+09:00 TRACE 8388 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [Product 1]
2024-12-24T12:14:07.130+09:00 TRACE 8388 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (2:DOUBLE) <- [300.0]
2024-12-24T12:14:07.130+09:00 TRACE 8388 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (2:DOUBLE) <- [200.0]
2024-12-24T12:14:07.130+09:00 TRACE 8388 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (3:INTEGER) <- [1]
2024-12-24T12:14:07.130+09:00 TRACE 8388 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (4:BIGINT) <- [1]
2024-12-24T12:14:07.130+09:00 TRACE 8388 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (3:INTEGER) <- [1]
2024-12-24T12:14:07.130+09:00 TRACE 8388 --- [locking] [ Thread-5] org.hibernate.orm.jdbc.bind : binding parameter (5:INTEGER) <- [0]
2024-12-24T12:14:07.131+09:00 TRACE 8388 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (4:BIGINT) <- [1]
2024-12-24T12:14:07.131+09:00 TRACE 8388 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (5:INTEGER) <- [0]
Exception in thread "Thread-5" org.opentest4j.AssertionFailedError: Expected org.springframework.dao.OptimisticLockingFailureException to be thrown, but nothing was thrown.
at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:73)
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:35)
at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3128)
at com.example.locking.product.ProductServiceTest.lambda$testOptimisticLocking$2(ProductServiceTest.java:31)
at java.base/java.lang.Thread.run(Thread.java:842)
2024-12-24T12:14:07.157+09:00 DEBUG 8388 --- [locking] [ Thread-4] org.hibernate.SQL :
select
p1_0.id,
p1_0.name,
p1_0.price,
p1_0.version
from
product p1_0
where
p1_0.id=?
2024-12-24T12:14:07.158+09:00 TRACE 8388 --- [locking] [ Thread-4] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
Exception in thread "Thread-4" org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.locking.product.Product#1]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:325)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:566)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:795)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:758)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:698)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:416)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
at com.example.locking.product.ProductService$$SpringCGLIB$$0.updateProductPrice(<generated>)
at com.example.locking.product.ProductServiceTest.lambda$testOptimisticLocking$0(ProductServiceTest.java:27)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.locking.product.Product#1]
at org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.identifiedResultsCheck(ModelMutationHelper.java:75)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.lambda$doStaticUpdate$9(UpdateCoordinatorStandard.java:785)
at org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.checkResults(ModelMutationHelper.java:50)
at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:141)
at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:55)
at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.doStaticUpdate(UpdateCoordinatorStandard.java:781)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.performUpdate(UpdateCoordinatorStandard.java:328)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.update(UpdateCoordinatorStandard.java:245)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:169)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:644)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:511)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:414)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:41)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1429)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:491)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2354)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1978)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:169)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:267)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562)
... 10 more
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2024-12-24T12:14:07.198+09:00 INFO 8388 --- [locking] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-12-24T12:14:07.200+09:00 DEBUG 8388 --- [locking] [ionShutdownHook] org.hibernate.SQL :
drop table if exists item cascade
2024-12-24T12:14:07.200+09:00 DEBUG 8388 --- [locking] [ionShutdownHook] org.hibernate.SQL :
drop table if exists product cascade
2024-12-24T12:14:07.205+09:00 INFO 8388 --- [locking] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2024-12-24T12:14:07.207+09:00 INFO 8388 --- [locking] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
> Task :test
BUILD SUCCESSFUL in 15s
4 actionable tasks: 3 executed, 1 up-to-date
PM 12:14:07: Execution finished ':test --tests "com.example.locking.product.ProductServiceTest.testOptimisticLocking"'.
- 낙관적 락을 적용했기 때문에 2번 째 작업을 수행하는 도중 버전 문제로 Exception이 발생하는 것을 확인할 수 있다.
데드 락(DeadLock)
ItemService(비관적 락 실습 코드에서 추가)
@Transactional(timeout = 1, isolation = Isolation.SERIALIZABLE)
public Item findItemById(Long itemId) {
return itemRepository.findById(itemId).orElse(null);
}
public void updateItemsQuantity(Long itemId1, Long itemId2) {
Item item1 = itemRepository.findByIdWithLock(itemId1);
item1.setQuantity(item1.getQuantity() + 10);
itemRepository.save(item1);
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Item item2 = itemRepository.findByIdWithLock(itemId2);
item1.setQuantity(item2.getQuantity() + 10);
itemRepository.save(item2);
}
ItemDeadLockTest
@SpringBootTest
public class ItemDeadLockTest {
private static final Logger logger = LoggerFactory.getLogger(ItemDeadLockTest.class);
@Autowired
private ItemService itemService;
@Autowired
private ItemRepository itemRepository;
@Test
public void testDeadlock() throws InterruptedException {
// 두 개의 아이템을 생성
Item item1 = new Item();
item1.setName("Item 1");
item1.setQuantity(100);
itemRepository.save(item1);
Item item2 = new Item();
item2.setName("Item 2");
item2.setQuantity(200);
itemRepository.save(item2);
// 스레드 동기화를 위한 CountDownLatch 설정
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Exception> exceptionInThread = new AtomicReference<>();
Thread thread1 = new Thread(() -> {
try {
logger.info("스레드 1 시작");
latch.await(); // 다른 스레드가 준비될 때까지 대기
logger.info("스레드 1: 아이템 1 -> 아이템 2 업데이트 시도");
itemService.updateItemsQuantity(item1.getId(), item2.getId());
logger.info("스레드 1: 업데이트 완료");
} catch (TransactionSystemException e) {
logger.error("스레드 1: 트랜잭션 오류 발생 - {}", e.getMessage());
exceptionInThread.set(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
logger.error("스레드 1: 알 수 없는 오류 발생", e);
exceptionInThread.set(e);
}
});
Thread thread2 = new Thread(() -> {
try {
logger.info("스레드 2 시작");
logger.info("스레드 2: 아이템 2 -> 아이템 1 업데이트 시도");
itemService.updateItemsQuantity(item2.getId(), item1.getId());
logger.info("스레드 2: 업데이트 완료");
} catch (TransactionSystemException e) {
logger.error("스레드 2: 트랜잭션 오류 발생 - {}", e.getMessage());
exceptionInThread.set(e);
} catch (Exception e) {
logger.error("스레드 2: 알 수 없는 오류 발생", e);
exceptionInThread.set(e);
}
});
// 두 스레드를 시작
thread1.start();
thread2.start();
// 스레드가 준비되었음을 알리고 실행
latch.countDown();
// 두 스레드가 종료될 때까지 대기
thread1.join();
thread2.join();
// 스레드 중 하나에서 TransactionSystemException이 발생했는지 확인
assertThrows(Exception.class, () -> {
if (exceptionInThread.get() != null) {
throw exceptionInThread.get();
}
});
}
}
테스트 결과
2024-12-24T12:30:25.387+09:00 INFO 38784 --- [locking] [ Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2024-12-24T12:30:25.420+09:00 DEBUG 38784 --- [locking] [ Test worker] org.hibernate.SQL :
drop table if exists item cascade
2024-12-24T12:30:25.423+09:00 DEBUG 38784 --- [locking] [ Test worker] org.hibernate.SQL :
drop table if exists product cascade
2024-12-24T12:30:25.430+09:00 DEBUG 38784 --- [locking] [ Test worker] org.hibernate.SQL :
create table item (
quantity integer,
id bigint generated by default as identity (start with 1),
name varchar(255),
primary key (id)
)
2024-12-24T12:30:25.431+09:00 DEBUG 38784 --- [locking] [ Test worker] org.hibernate.SQL :
create table product (
price float(53),
version integer,
id bigint generated by default as identity (start with 1),
name varchar(255),
primary key (id)
)
2024-12-24T12:30:25.439+09:00 INFO 38784 --- [locking] [ Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-12-24T12:30:25.976+09:00 INFO 38784 --- [locking] [ Test worker] o.s.d.j.r.query.QueryEnhancerFactory : Hibernate is in classpath; If applicable, HQL parser will be used.
2024-12-24T12:30:27.274+09:00 WARN 38784 --- [locking] [ Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-12-24T12:30:28.052+09:00 INFO 38784 --- [locking] [ Test worker] c.example.locking.item.ItemDeadLockTest : Started ItemDeadLockTest in 7.762 seconds (process running for 9.696)
2024-12-24T12:30:28.737+09:00 DEBUG 38784 --- [locking] [ Test worker] org.hibernate.SQL :
/* insert for
com.example.locking.item.Item */insert
into
item (name, quantity, id)
values
(?, ?, default)
2024-12-24T12:30:28.755+09:00 TRACE 38784 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [Item 1]
2024-12-24T12:30:28.756+09:00 TRACE 38784 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (2:INTEGER) <- [100]
2024-12-24T12:30:28.825+09:00 DEBUG 38784 --- [locking] [ Test worker] org.hibernate.SQL :
/* insert for
com.example.locking.item.Item */insert
into
item (name, quantity, id)
values
(?, ?, default)
2024-12-24T12:30:28.826+09:00 TRACE 38784 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [Item 2]
2024-12-24T12:30:28.826+09:00 TRACE 38784 --- [locking] [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (2:INTEGER) <- [200]
2024-12-24T12:30:28.829+09:00 INFO 38784 --- [locking] [ Thread-5] c.example.locking.item.ItemDeadLockTest : 스레드 2 시작
2024-12-24T12:30:28.829+09:00 INFO 38784 --- [locking] [ Thread-4] c.example.locking.item.ItemDeadLockTest : 스레드 1 시작
2024-12-24T12:30:28.829+09:00 INFO 38784 --- [locking] [ Thread-5] c.example.locking.item.ItemDeadLockTest : 스레드 2: 아이템 2 -> 아이템 1 업데이트 시도
2024-12-24T12:30:28.829+09:00 INFO 38784 --- [locking] [ Thread-4] c.example.locking.item.ItemDeadLockTest : 스레드 1: 아이템 1 -> 아이템 2 업데이트 시도
2024-12-24T12:30:28.892+09:00 ERROR 38784 --- [locking] [ Thread-4] c.example.locking.item.ItemDeadLockTest : 스레드 1: 알 수 없는 오류 발생
org.springframework.dao.InvalidDataAccessApiUsageException: Query requires transaction be in progress, but no transaction is known to be in progress
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:400) ~[spring-orm-6.2.1.jar:6.2.1]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246) ~[spring-orm-6.2.1.jar:6.2.1]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:560) ~[spring-orm-6.2.1.jar:6.2.1]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:343) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) ~[spring-aop-6.2.1.jar:6.2.1]
at jdk.proxy3/jdk.proxy3.$Proxy119.findByIdWithLock(Unknown Source) ~[na:na]
at com.example.locking.item.ItemService.updateItemsQuantity(ItemService.java:29) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:723) ~[spring-aop-6.2.1.jar:6.2.1]
at com.example.locking.item.ItemService$$SpringCGLIB$$0.updateItemsQuantity(<generated>) ~[main/:na]
at com.example.locking.item.ItemDeadLockTest.lambda$testDeadlock$0(ItemDeadLockTest.java:47) ~[test/:na]
at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na]
Caused by: jakarta.persistence.TransactionRequiredException: Query requires transaction be in progress, but no transaction is known to be in progress
at org.hibernate.internal.AbstractSharedSessionContract.prepareForQueryExecution(AbstractSharedSessionContract.java:528) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.beforeQuery(AbstractSelectionQuery.java:171) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.beforeQueryHandlingFetchProfiles(AbstractSelectionQuery.java:159) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:140) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.getSingleResult(AbstractSelectionQuery.java:275) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:419) ~[spring-orm-6.2.1.jar:6.2.1]
at jdk.proxy3/jdk.proxy3.$Proxy153.getSingleResult(Unknown Source) ~[na:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:224) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:152) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:140) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ~[spring-tx-6.2.1.jar:6.2.1]
... 15 common frames omitted
2024-12-24T12:30:28.892+09:00 ERROR 38784 --- [locking] [ Thread-5] c.example.locking.item.ItemDeadLockTest : 스레드 2: 알 수 없는 오류 발생
org.springframework.dao.InvalidDataAccessApiUsageException: Query requires transaction be in progress, but no transaction is known to be in progress
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:400) ~[spring-orm-6.2.1.jar:6.2.1]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246) ~[spring-orm-6.2.1.jar:6.2.1]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:560) ~[spring-orm-6.2.1.jar:6.2.1]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:343) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) ~[spring-aop-6.2.1.jar:6.2.1]
at jdk.proxy3/jdk.proxy3.$Proxy119.findByIdWithLock(Unknown Source) ~[na:na]
at com.example.locking.item.ItemService.updateItemsQuantity(ItemService.java:29) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:723) ~[spring-aop-6.2.1.jar:6.2.1]
at com.example.locking.item.ItemService$$SpringCGLIB$$0.updateItemsQuantity(<generated>) ~[main/:na]
at com.example.locking.item.ItemDeadLockTest.lambda$testDeadlock$1(ItemDeadLockTest.java:64) ~[test/:na]
at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na]
Caused by: jakarta.persistence.TransactionRequiredException: Query requires transaction be in progress, but no transaction is known to be in progress
at org.hibernate.internal.AbstractSharedSessionContract.prepareForQueryExecution(AbstractSharedSessionContract.java:528) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.beforeQuery(AbstractSelectionQuery.java:171) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.beforeQueryHandlingFetchProfiles(AbstractSelectionQuery.java:159) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:140) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at org.hibernate.query.spi.AbstractSelectionQuery.getSingleResult(AbstractSelectionQuery.java:275) ~[hibernate-core-6.6.4.Final.jar:6.6.4.Final]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:419) ~[spring-orm-6.2.1.jar:6.2.1]
at jdk.proxy3/jdk.proxy3.$Proxy153.getSingleResult(Unknown Source) ~[na:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:224) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:152) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:140) ~[spring-data-jpa-3.4.1.jar:3.4.1]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) ~[spring-data-commons-3.4.1.jar:3.4.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.1.jar:6.2.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.1.jar:6.2.1]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ~[spring-tx-6.2.1.jar:6.2.1]
... 15 common frames omitted
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2024-12-24T12:30:28.949+09:00 INFO 38784 --- [locking] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-12-24T12:30:28.952+09:00 DEBUG 38784 --- [locking] [ionShutdownHook] org.hibernate.SQL :
drop table if exists item cascade
2024-12-24T12:30:28.953+09:00 DEBUG 38784 --- [locking] [ionShutdownHook] org.hibernate.SQL :
drop table if exists product cascade
2024-12-24T12:30:28.958+09:00 INFO 38784 --- [locking] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2024-12-24T12:30:28.962+09:00 INFO 38784 --- [locking] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
> Task :test
BUILD SUCCESSFUL in 14s
4 actionable tasks: 3 executed, 1 up-to-date
PM 12:30:29: Execution finished ':test --tests "com.example.locking.item.ItemDeadLockTest.testDeadlock"'.
- 트랜잭션 2개가 서로 무한 대기하는 데드락이 발생하는 것을 알 수 있다.
정리
- DB의 데이터 무결성을 보장하기 위한 Lock 방식에 대해 배워보고 실제로 테스트해 보았다.
'자바 심화 > TIL' 카테고리의 다른 글
동시성 제어 시점과 데이터 일관성 유지 관점 (1) | 2024.12.30 |
---|---|
Redis - Redisson (2) | 2024.12.27 |
장애 대응 (1) | 2024.12.24 |
시큐어 코딩(Secure Coding) (0) | 2024.12.23 |
모니터링 - Slack Alert 보내기 (1) | 2024.12.20 |