@Test
void 주문_취소_동시성_테스트() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1000);
CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 1; i <= 1000; i++) {
Long finalI = (long) i + 63831;
executorService.execute(() -> {
주문_취소하기(finalI);
countDownLatch.countDown();
});
}
countDownLatch.await();
Products products = productRepository.findById(1L).get();
assertThat(products.getStock()).isEqualTo(1000);
}
@Transactional // 이 녀석이 제대로 실행되지 않아서 트랜잭션이 적용되지 않음
public void 주문_취소하기(Long orderId) {
Orders order = orderQueryRepository.findOrderById(orderId);
재고_상태변경(order.getProducts());
재고_늘리기(order.getProducts(), order.getProductNum());
orderRepository.delete(order);
}
public void 재고_늘리기(Products products, int quantity){
products.increaseStock(quantity);
}
public void 재고_상태변경(Products products) {
if (products.getStock() == 0 && products.isOnSale()) {
products.updateOnSale(false);
} else if (products.getStock() == 0 && !products.isOnSale()){
products.updateOnSale(true);
}
}
주문_취소하기()
메소드를 실행했을 때 @Transactional 이 붙어있기 때문에 자동으로 트랜잭션이 실행될줄 알았지만 해당 어노테이션이 정상적으로 실행되지 않았기 때문에 TransactionalRequiredException
발생했다.
Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1644)
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1617)
at org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1665)
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:406)
at jdk.proxy2/jdk.proxy2.$Proxy151.getSingleResult(Unknown Source)
at com.querydsl.jpa.impl.AbstractJPAQuery.getSingleResult(AbstractJPAQuery.java:214)
at com.querydsl.jpa.impl.AbstractJPAQuery.fetchOne(AbstractJPAQuery.java:326)
at com.example.showmethemany.Repository.OrderQueryRepository.findOrderById(OrderQueryRepository.java:31)
at com.example.showmethemany.Repository.OrderQueryRepository$$FastClassBySpringCGLIB$$4d995064.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
... 9 more
스프링에서는 @Transactional
어노테이션을 사용해서 트랜잭션을 적용하기 위한 프록시 객체를 생성한 후에 프록시 객체가 @Transactional
어노테이션이 적용된 빈을 대신해서 호출해준다. 하지만 스프링에서 관리되지 않는 클래스에서는 프록시 객체를 생성할 수 없기 때문에 우리가 자주 사용하던 @Component
@Service
@Repository
와 같은 어노테이션을 사용해서 스프린 빈에 등록을 해주어야 한다.
하지만 Test 클래스에는 스프링 빈에 등록이 된적이 없기 때문에 결과적으로 @Transactional
이 작동하지 않았던 것이다.
테스트 클래스에서는 스프링에서 트랜잭션을 관리하기 위해 만들어진 PlatformTransactionManager
인터페이스를 주입 받아서 트랜잭션을 적용해줄 수 있다. (JDBC, Hibernate, JPA도 내부적으로 PlatformTransactionManager
를 사용해서 트랜잭션을 관리한다)