Я переместил синхронный процесс в асинхронный, и теперь у меня есть некоторые проблемы с поддержкой интеграционных тестов.Это связано с тем, что когда вы создаете новый поток внутри метода @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);
}
}
}