J.Olufsen
J.Olufsen

Reputation: 13915

How to manually handle Spring 4 transactions?

How to programmatically control transaction boundaries within single @Test method? Spring 4.x documentation has some clues but I think I missing something since the test throws error:

java.lang.IllegalStateException: 
Cannot start a new transaction without ending the existing transaction first.

Test

import com.hibernate.query.performance.config.ApplicationConfig;
import com.hibernate.query.performance.config.CachingConfig;
import com.hibernate.query.performance.persistence.model.LanguageEntity;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.test.context.transaction.TestTransaction;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.PersistenceContext;
import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ApplicationConfig.class, CachingConfig.class }, loader = AnnotationConfigContextLoader.class)
@PersistenceContext
@Transactional(transactionManager = "hibernateTransactionManager")
@TestExecutionListeners({})
public class EHCacheTest extends AbstractTransactionalJUnit4SpringContextTests {

    private static Logger logger = LoggerFactory.getLogger(EHCacheTest.class);

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        logger.info("setUpBeforeClass()");
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        logger.info("tearDownAfterClass()");
    }

    @Autowired
    private SessionFactory sessionFactory;

    @Test
    public void testTransactionCaching(){
        TestTransaction.start();
        Session session = sessionFactory.getCurrentSession();
        System.out.println(session.get(LanguageEntity.class, 1));
        Query query = session.createQuery("from LanguageEntity le where le.languageId < 10").setCacheable(true).setCacheRegion("language");
        @SuppressWarnings("unchecked")
        List<LanguageEntity> customerEntities = query.list();
        System.out.println(customerEntities);
        session.getTransaction().commit();

        TestTransaction.flagForCommit();
        TestTransaction.end();

        // Second Transaction
        TestTransaction.start();

        Session sessionNew =  sessionFactory.getCurrentSession();
        System.out.println(sessionNew.get(LanguageEntity.class, 1));
        Query anotherQuery = sessionNew.createQuery("from LanguageEntity le where le.languageId < 10");
        anotherQuery.setCacheable(true).setCacheRegion("language");
        @SuppressWarnings("unchecked")
        List<LanguageEntity> languagesFromCache = anotherQuery.list();
        System.out.println(languagesFromCache);
        sessionNew.getTransaction().commit();

        TestTransaction.flagForCommit();
        TestTransaction.end();
    }
}

UPDATE

One more detail:

All occurences session.getTransaction().commit(); must be removed since they interrupt transaction workflow.

Upvotes: 1

Views: 4334

Answers (1)

Ali Dehghani
Ali Dehghani

Reputation: 48153

TL;DR


In order to avoid this problem, just remove the first line of the test method and use the already available transaction:

@Test
public void testTransactionCaching() {
    // Remove this => TestTransaction.start(); 
    // Same as before
}

Detailed Answer

When you annotate your test class with @Transactional or extending the AbstractTransactionalJUnit4SpringContextTests:

// Other annotations
@Transactional(transactionManager = "hibernateTransactionManager")
public class EHCacheTest extends AbstractTransactionalJUnit4SpringContextTests { ... }

Each test method within that class will be run within a transaction. To be more precise, Spring Test Context (By using TransactionalTestExecutionListener) would open a transaction in the beginning of each test method and roll it back after completion of the test.

So, your testTransactionCaching test method:

@Test
public void testTransactionCaching() { ... }

Would have an open transaction in the beginning and you're trying to open another one manually by:

TestTransaction.start();

Hence the error:

Cannot start a new transaction without ending the existing transaction first.

In order to avoid this problem, just remove the first line of the test method and use that already available transaction. The other TestTransaction.* method calls are OK but just remove the first one.

Upvotes: 4

Related Questions