Multi-threaded JPA Test doesn't Roll Back. Why?

80 Views Asked by At

Below is part of simple @DataJPATest. Two tests are identical.
It rolls back without problem when it executed in a single thread.


    @Test
    void SingleThreadTest1() throws RequesterException, InterruptedException {
        System.out.println("(before)count: " + logRepository.count());
        App app = appRepository.findByName("TestApp").orElseThrow();
        myService.loggingTransaction(app);
        System.out.println("(after)count: " + logRepository.count());
    }

    @Test
    void SingleThreadTest2() throws RequesterException, InterruptedException {
        System.out.println("(before)count: " + logRepository.count());
        App app = appRepository.findByName("TestApp").orElseThrow();
        myService.loggingTransaction(app);
        System.out.println("(after)count: " + logRepository.count());
    }

Result:

    #SingleThreadTest1
    (before)count: 0
    (after)count: 1

    #SingleThreadTest2
    (before)count: 0
    (after)count: 1

But It doesn't roll back when operating in multi-threaded.

@DataJpaTest
class ServiceTest {
    static final int CONCURRENCY = 1;

    @Test
    void MultiThreadTest1() throws RequesterException, InterruptedException {
        System.out.println("(before)count: " + logRepository.count());
        App app = appRepository.findByName("TestApp").orElseThrow();
        // --------From Here: Spawn sub thread---------
        ThreadPoolExecutor executor = new ThreadPoolExecutor(CONCURRENCY, CONCURRENCY, 1, TimeUnit.MINUTES, new SynchronousQueue<>());
        executor.prestartAllCoreThreads();
        List<Callable<OptoutMessage>> callables = IntStream.range(0, CONCURRENCY)
                .<Callable<OptoutMessage>>mapToObj(i -> () -> myService.loggingTransaction(app))
                .toList();
        List<Future<OptoutMessage>> futures = executor.invokeAll(callables);
        executor.shutdownNow();
        // ------------------Ultil here----------------
        System.out.println("(after)count: " + logRepository.count());
    }
    
    @Test
    void MultiThreadTest2() throws RequesterException, InterruptedException {
        System.out.println("(before)count: " + logRepository.count());
        App app = appRepository.findByName("TestApp").orElseThrow();
        // --------From Here: Spawn sub thread---------
        ThreadPoolExecutor executor = new ThreadPoolExecutor(CONCURRENCY, CONCURRENCY, 1, TimeUnit.MINUTES, new SynchronousQueue<>());
        executor.prestartAllCoreThreads();
        List<Callable<OptoutMessage>> callables = IntStream.range(0, CONCURRENCY)
                .<Callable<OptoutMessage>>mapToObj(i -> () -> myService.loggingTransaction(app))
                .toList();
        List<Future<OptoutMessage>> futures = executor.invokeAll(callables);
        executor.shutdownNow();
        // ------------------Ultil here----------------
        System.out.println("(after)count: " + logRepository.count());
    }

Result:

    #MultiThreadTest1
    (before)count: 0
    (after)count: 1

    #MultiThreadTest2
    (before)count: 1 // <- IT SHOULD BE 0 !!!
    (after)count: 2 

Question:

  1. Why @DataJPATest doesn't rolls back when transaction when it occurs in sub thread?
  2. What should I do to roll back the data after @Test under this situation?
1

There are 1 best solutions below

0
Ole Zechmann On

We recently had a similar issue.

Question 1: I cannot give you a sufficient answer, as I also don't know exactly why it is not rolling back.

Question 2: What worked for us was to add the annotation

@DirtiesContext

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/annotation/DirtiesContext.html