83

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().

Eric B.
  • 23,425
  • 50
  • 169
  • 316
  • You might research if there's a way to have the spawned threads participate in the transaction so they will see the uncommitted results. – Jim Garrison Jun 21 '14 at 04:06
  • If the method is `@Transactional`, returning from the method commits the transaction. So why not just return from the method? – Raedwald Jun 21 '14 at 10:34
  • Conceptually unit tests should likely not be transactional, and with Spring's model it also makes no practical sense. You should be looking at integration tests using Spring TestContext which has tools to help with transactions: http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/testing.html#testing-tx – Matt Whipple Jun 21 '14 at 13:06
  • @JimGarrison Actually the whole point of my unit test is to test out parallel transactions and validate that there are no concurrency issues in the transactions. – Eric B. Jun 22 '14 at 01:16
  • @Raedwald if I return from the method how do I continue my test? I need the commit before my threads spawn as the threads use the data that is created before they are spawned. – Eric B. Jun 22 '14 at 01:18
  • Btw., if you are running with repositories you have a flush() method on your JPARepository. See http://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html#flush-- – Josh Aug 22 '16 at 13:54

3 Answers3

105

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.

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
Martin Frey
  • 10,025
  • 4
  • 25
  • 30
  • Brilliant. Hadn't thought of that. Have to break up my test method a little bit, which I'm not too thrilled about, but it works great. – Eric B. Jun 22 '14 at 01:43
  • 6
    @MartinFrey Why "In another class" ? – Basemasta Sep 07 '14 at 17:31
  • 34
    As spring works with proxies to achieve this feature (alot of others too) you have to use another class so spring is able to trigger the transactions. Once you are inside a class you will not go through the proxy again. – Martin Frey Sep 07 '14 at 18:56
  • Can this be an inner class? – Gleeb May 11 '15 at 08:36
  • 1
    Could be, as long as it's a spring bean. Did not test it like this. Have a try :) – Martin Frey May 11 '15 at 12:18
  • Could you post a code excerpt? Doing as you are but only one transaction ever commits at the end of the test – PragmaticProgrammer Jun 29 '18 at 16:06
  • How is `REQUIRES_NEW` is going to fix the issue? As you said, it just suspends the parent transaction and doesn't necessarily commits it (parent). Does it mean that the parent waits for the child to return and only then the commit of the parent happens? If so, then it's not a 100% solution due to the way threading works. It just increases the chances of avoiding the race condition – Aladin Jul 30 '20 at 09:31
  • If you have a nested transaction, I assume that the point is you don't want to suspend the outer transaction. E.g. in case of streams that you want to keep open. – html_programmer Jun 11 '21 at 08:18
28

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

}

Pieter
  • 2,745
  • 14
  • 17
  • Thanks for the idea. Had considered that, but looked like a lot of work/overkill for a simple problem. Had presumed that there had to be something a lot simpler. Breaking it into a separate method with Propagation.REQUIRES_NEW accomplished just that (see @MartinFrey's answer). – Eric B. Jun 22 '14 at 01:44
  • 1
    For my case it works only with `transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);` See code [here](https://stackoverflow.com/a/46415060/548473) – Grigory Kislin Sep 25 '17 at 22:23
7

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.

G. Demecki
  • 10,145
  • 3
  • 58
  • 58
  • Hi, I wonder if this holds: When creating an integration test on a statement saving an object, it is recommended to flush the entity manager so as to avoid any false negative, that is, to avoid a test running fine but whose operation would fail when run in production. Indeed, the test may run fine simply because the first level cache is not flushed and no writing hits the database. To avoid this false negative integration test use an explicit flush in the test body. – Stephane Oct 08 '15 at 16:06
  • 2
    @StephaneEybert IMO just flushing the `EntityManager` is more like a hack :-) But it depends on your needs and type of tests you are doing. For real integration tests (that should behave exactly as in production) the answer is: definitely do not use `@Transactional` around test. But drawback also exists: you have to setup a database into well known state before every such test. – G. Demecki Oct 09 '15 at 06:11