Как вручную принудительно совершить фиксацию в методе @Transactional?
Я использую Spring/Spring -data-JPA, и мне нужно вручную принудительно зафиксировать фиксацию в unit test. Мой вариант использования заключается в том, что я выполняю многопоточный тест, в котором я должен использовать данные, которые сохраняются до появления нитей.
К сожалению, учитывая, что тест выполняется в транзакции @Transactional
, даже flush
не делает его доступным для порожденных потоков.
@Transactional
public void testAddAttachment() throws Exception{
final Contract c1 = contractDOD.getNewTransientContract(15);
contractRepository.save(c1);
// Need to commit the saveContract here, but don't know how!
em.getTransaction().commit();
List<Thread> threads = new ArrayList<>();
for( int i = 0; i < 5; i++){
final int threadNumber = i;
Thread t = new Thread( new Runnable() {
@Override
@Transactional
public void run() {
try {
// do stuff here with c1
// sleep to ensure that the thread is not finished before another thread catches up
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
threads.add(t);
t.start();
}
// have to wait for all threads to complete
for( Thread t : threads )
t.join();
// Need to validate test results. Need to be within a transaction here
Contract c2 = contractRepository.findOne(c1.getId());
}
Я пытался использовать диспетчер сущности, но получаю сообщение об ошибке, когда я это делаю:
org.springframework.dao.InvalidDataAccessApiUsageException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead; nested exception is java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:293)
at org.springframework.orm.jpa.aspectj.JpaExceptionTranslatorAspect.ajc$afterThrowing$org_springframework_orm_jpa_aspectj_JpaExceptionTranslatorAspect$1$18a1ac9(JpaExceptionTranslatorAspect.aj:33)
Есть ли способ совершить транзакцию и продолжить ее? Я не смог найти какой-либо метод, который позволяет мне называть commit()
.
Ответы
Ответ 1
У меня был аналогичный случай использования во время тестирования прослушивателей событий в спящем режиме, которые вызываются только при фиксации.
Решение заключалось в том, чтобы преобразовать код в постоянный в другой метод, аннотированный с помощью REQUIRES_NEW
. (В другом классе) Таким образом создается новая транзакция, и при возврате метода выдается флеш/фиксация.
![Tx prop REQUIRES_NEW]()
Имейте в виду, что это может повлиять на все остальные тесты! Поэтому напишите их соответственно, или вам нужно убедиться, что вы можете очистить после запуска теста.
Ответ 2
Почему вы не используете spring TransactionTemplate
для программного управления транзакциями? Вы также можете перестроить свой код, чтобы каждый "блок транзакций" имел свой собственный метод @Transactional
, но, учитывая, что это тест, я бы выбрал программный контроль ваших транзакций.
Также обратите внимание, что аннотация @Transactional
на вашей runnable не будет работать (если вы не используете aspectj), поскольку runnables не управляются spring!
@RunWith(SpringJUnit4ClassRunner.class)
//other spring-test annotations; as your database context is dirty due to the committed transaction you might want to consider using @DirtiesContext
public class TransactionTemplateTest {
@Autowired
PlatformTransactionManager platformTransactionManager;
TransactionTemplate transactionTemplate;
@Before
public void setUp() throws Exception {
transactionTemplate = new TransactionTemplate(platformTransactionManager);
}
@Test //note that there is no @Transactional configured for the method
public void test() throws InterruptedException {
final Contract c1 = transactionTemplate.execute(new TransactionCallback<Contract>() {
@Override
public Contract doInTransaction(TransactionStatus status) {
Contract c = contractDOD.getNewTransientContract(15);
contractRepository.save(c);
return c;
}
});
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; ++i) {
executorService.execute(new Runnable() {
@Override //note that there is no @Transactional configured for the method
public void run() {
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
// do whatever you want to do with c1
return null;
}
});
}
});
}
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.SECONDS);
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
// validate test results in transaction
return null;
}
});
}
}
Ответ 3
Я знаю, что из-за этого уродливого анонимного внутреннего использования класса TransactionTemplate
выглядит не очень хорошо, но когда по какой-то причине мы хотим иметь тестовый метод транзакционного IMHO, он наиболее гибкий вариант.
В некоторых случаях (это зависит от типа приложения) наилучшим способом использования транзакций в тестах Spring является отключенный @Transactional
в методах тестирования. Зачем? Потому что @Transactional
может привести к множеству ложноположительных тестов. Вы можете посмотреть эту образец статьи, чтобы узнать подробности. В таких случаях TransactionTemplate
может быть идеальным для контроля границ транзакций, когда мы хотим этого элемента управления.