RCola RCola - 6 months ago 159
Java Question

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.

Answer

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.

Comments