Java 如何在@Transactional 方法中手动强制提交?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24338150/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to manually force a commit in a @Transactional method?
提问by Eric B.
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.
我正在使用 Spring / Spring-data-JPA 并发现自己需要在单元测试中手动强制提交。我的用例是我正在做一个多线程测试,其中我必须使用在产生线程之前持久化的数据。
Unfortunately, given that the test is running in a @Transactional
transaction, even a flush
does not make it accessible to the spawned threads.
不幸的是,鉴于测试在@Transactional
事务中运行,即使 aflush
也不能使其对生成的线程进行访问。
@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_JpaExceptionTranslatorAspecta1ac9(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()
.
有没有办法提交事务并继续它?我一直无法找到任何允许我调用commit()
.
采纳答案by Martin Frey
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.
解决方案是将要持久化的代码包装到另一个用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.
请记住,这可能会影响所有其他测试!因此,相应地编写它们,或者您需要确保在测试运行后可以进行清理。
回答by Pieter
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.
为什么不使用 springTransactionTemplate
以编程方式控制事务?您还可以重组您的代码,以便每个“交易块”都有自己的@Transactional
方法,但鉴于这是一个测试,我会选择对您的交易进行编程控制。
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!
另请注意,@Transactional
runnable 上的注释将不起作用(除非您使用 aspectj),因为 runnable 不是由 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;
}
});
}
}
}
回答by G. Demecki
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 flexibleoption.
我知道由于这种丑陋的匿名内部类使用TransactionTemplate
看起来不太好,但是当出于某种原因我们想要一个测试方法事务性恕我直言时,它是最灵活的选择。
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 articleto find out details. In such cases TransactionTemplate
can be perfect for controlling transaction boundries when we want that control.
在某些情况下(取决于应用程序类型),在 Spring 测试中使用事务的最佳方式是关闭@Transactional
测试方法。为什么?因为@Transactional
可能会导致许多假阳性测试。您可以查看此示例文章以了解详细信息。在这种情况下TransactionTemplate
,当我们需要控制时,可以完美地控制事务边界。