1. 테스트 코드에서 문제가 발생한 부분

@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

2. 왜 @Transactional이 작동되지 않았을까?

스프링에서는 @Transactional 어노테이션을 사용해서 트랜잭션을 적용하기 위한 프록시 객체를 생성한 후에 프록시 객체가 @Transactional 어노테이션이 적용된 빈을 대신해서 호출해준다. 하지만 스프링에서 관리되지 않는 클래스에서는 프록시 객체를 생성할 수 없기 때문에 우리가 자주 사용하던 @Component @Service @Repository 와 같은 어노테이션을 사용해서 스프린 빈에 등록을 해주어야 한다.

하지만 Test 클래스에는 스프링 빈에 등록이 된적이 없기 때문에 결과적으로 @Transactional 이 작동하지 않았던 것이다.

3. 그렇다면 테스트 클래스에서는 어떻게 트랜잭션을 적용해야 할까?

테스트 클래스에서는 스프링에서 트랜잭션을 관리하기 위해 만들어진 PlatformTransactionManager 인터페이스를 주입 받아서 트랜잭션을 적용해줄 수 있다. (JDBC, Hibernate, JPA도 내부적으로 PlatformTransactionManager 를 사용해서 트랜잭션을 관리한다)