【问题标题】:How to manually handle Spring 4 transactions?如何手动处理 Spring 4 事务?
【发布时间】:2016-05-20 13:28:25
【问题描述】:

如何在单个@Test 方法中以编程方式控制事务边界? Spring 4.x 文档有一些 clues 但我想我错过了一些东西,因为测试抛出错误:

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

测试

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

更新

更多细节:

所有出现的session.getTransaction().commit(); 必须删除,因为它们会中断事务工作流程。

【问题讨论】:

  • 您可以放心地删除以下内容,因为它们在这种情况下是多余的:@RunWith(SpringJUnit4ClassRunner.class)@TestExecutionListeners({})
  • 此外,@PersistenceContext 在测试类中没有任何意义。所以你可以安全地删除它。

标签: java spring hibernate transactions spring-test


【解决方案1】:

TL;DR


为了避免这个问题,只要去掉测试方法的第一行,使用已经可用的事务即可:

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

详细解答

当您使用 @Transactional 注释您的测试类或扩展 AbstractTransactionalJUnit4SpringContextTests 时:

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

该类中的每个测试方法都将在事务中运行。更准确地说,Spring Test Context(通过使用TransactionalTestExecutionListener)会在每个测试方法开始时打开一个事务,并在测试完成后将其回滚。

那么,你的testTransactionCaching 测试方法:

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

一开始会有一个未完成的交易,而您正试图通过以下方式手动打开另一个交易:

TestTransaction.start();

因此错误:

在不结束现有事务的情况下无法开始新事务 首先。

为了避免这个问题,只需删除测试方法的第一行并使用已经可用的事务。其他 TestTransaction.* 方法调用都可以,但只需删除第一个。

【讨论】:

  • 很好的答案,除了关于@TestExecutionListeners({})的部分。 AbstractTransactionalJUnit4SpringContextTests 带有 @TestExecutionListeners 注释。因此,测试类上的空声明只是将 nothing 附加到已声明的侦听器。也就是说,它的效果为零,不会禁用TransactionalTestExecutionListener
  • @SamBrannen 感谢山姆的反馈...我编辑了答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-31
  • 2013-08-26
  • 1970-01-01
  • 2019-01-17
  • 2019-11-25
  • 1970-01-01
相关资源
最近更新 更多