Поток весенней тестовой транзакции - PullRequest
0 голосов
/ 05 июня 2018

Я переместил синхронный процесс в асинхронный, и теперь у меня есть некоторые проблемы с поддержкой интеграционных тестов.Это связано с тем, что когда вы создаете новый поток внутри метода @Transactional, а затем вызываете новый @Transactional, Spring создает новую транзакцию.

Во время интеграционных тестов проблема возникает с тестами @Transactional.Похоже, что транзакция потока откатывается до завершения теста из-за TransactionalTestExecutionListener в конфигурации теста.

Я пробовал много вещей, таких как - автоматическое связывание EntityManager и ручная очистка после завершения потока - используя @Rollback вместо @Transactionalв тестовых методах - управление транзакциями с помощью TestTransaction - совместное использование @Rollback и TestTransaction

Вот упрощенный исходный код:

public interface MyService{
    public void doThing(someArgs...);

    public void updateThings(someArgs...);
}

@Service
public class MyServiceImpl implements MyService{

    @Autowired
    private AsynchronousFutureHandlerService futureService;

    @Autowired
    @Qualifier("myExecutorService")
    private ScheduledExecutorService myExecutorService;

    @Transactional
    @Override
    public void doThing(someArgs...){
        doThingAsync(someArgs...);
    }

    private void doThingAsync(someArgs...){
        AsynchronousHandler runnable = applicationContext.getBean(
                AsynchronousHandler.class, someArgs...);

        //as we are executing some treatment in a new Thread, a new transaction is automatically created
        Future<?> future = myExecutorService.submit(runnable);

        //keep track of thread execution
        futureService.addFutures(future);
    }

    @Override
    @Transactional
    public void updateThings(someArgs...){
        //do udpate stuff
    }
}

/**
*   very basic solution to improve later to consult thread state
*/
@Service
public class AsynchronousFutureHandlerService {

    //TODO : pass to ThreadSafe collection
    private List<Future<?>> futures = new ArrayList<>();

    public void addTheoreticalApplicationFuture(Future<?> future){
        futures.add(future);
        this.deleteJobsDone();
    }

    public boolean isThreadStillRunning(){
        boolean stillRunning = false;
        for(Future<?> f : futures){
            if(!f.isDone()){
                stillRunning = true;
                break;
            }
        }
        return stillRunning;
    }

    public void deleteJobsDone(){
        this.futures.removeIf(f -> f.isDone());
    }
}

@Component
@Scope("prototype")
public class AsynchronousHandler implements Runnable {

    @Autowired
    private MyService myService;

    @Override
    public void run() {
        myService.updateThings(...); //updates data in DB
        ...
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DataSetTestExecutionListener.class,
        TransactionalTestExecutionListener.class })
@DataSet(dbType = DBType.H2, locations = { "classpath:dataset.xml" })

public class MyServiceTest{

    @Autowired
    private MyService myService;

    @Autowired
    private AsynchronousFutureHandlerService futureService;

    @Test
    @Transactional
    public void test_doThings(){
        myService.doThings(someArgs...);
        waitUpdateFinish();

        Assert.assertEquals(...); //fails here because Thread transaction has been rollbacked
    }

    private void waitUpdateFinish() throws InterruptedException{
        while(futureService.isThreadStillRunning()){
            Thread.sleep(500);
        }
    }
}
...