How to manually force a commit in a @Transactional method?

I'm using Spring / Spring-data-JPA and find myself needing to manually force a commit in a unit test. My use case is that I am doing a multi-threaded test in which I have to use data that is persisted before the threads are spawned.

Unfortunately, given that the test is running in a @Transactional transaction, even a flush does not make it accessible to the spawned threads.

   @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());
}

I've tried using the entity manager to, but get an error message when I do:

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)

Is there any way to commit the transaction and continue it? I have been unable to find any method that allows me to call a commit().

158395 次浏览

I had a similar use case during testing hibernate event listeners which are only called on commit.

The solution was to wrap the code to be persistent into another method annotated with REQUIRES_NEW. (In another class) This way a new transaction is spawned and a flush/commit is issued once the method returns.

Tx prop REQUIRES_NEW

Keep in mind that this might influence all the other tests! So write them accordingly or you need to ensure that you can clean up after the test ran.

Why don't you use spring's TransactionTemplate to programmatically control transactions? You could also restructure your code so that each "transaction block" has it's own @Transactional method, but given that it's a test I would opt for programmatic control of your transactions.

Also note that the @Transactional annotation on your runnable won't work (unless you are using aspectj) as the runnables aren't managed by 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;
}
});
}

}

I know that due to this ugly anonymous inner class usage of TransactionTemplate doesn't look nice, but when for some reason we want to have a test method transactional IMHO it is the most flexible option.

In some cases (it depends on the application type) the best way to use transactions in Spring tests is a turned-off @Transactional on the test methods. Why? Because @Transactional may leads to many false-positive tests. You may look at this sample article to find out details. In such cases TransactionTemplate can be perfect for controlling transaction boundries when we want that control.