私はSpring/Spring-data-JPAを使用していますが、ユニットテストで手動でコミットを強制する必要があることに気付きました。私のユースケースは、スレッドが生成される前に保持されているデータを使用する必要があるマルチスレッドテストを実行していることです。
残念ながら、テストが@Transactional
トランザクションで実行されていることを考えると、flush
であっても、生成されたスレッドにアクセスできません。
@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());
}
エンティティマネージャーを使用してみましたが、実行するとエラーメッセージが表示されます。
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)
トランザクションをコミットして続行する方法はありますか? commit()
を呼び出すことができるメソッドを見つけることができませんでした。
コミット時にのみ呼び出されるhibernateイベントリスナーのテスト中に、同様のユースケースがありました。
解決策は、REQUIRES_NEW
アノテーションが付けられた別のメソッドに永続的になるようにコードをラップすることでした。 (別のクラス)この方法では、新しいトランザクションが生成され、メソッドが戻るとフラッシュ/コミットが発行されます。
これは他のすべてのテストに影響する可能性があることに注意してください!したがって、それらを適宜記述するか、テストの実行後にクリーンアップできることを確認する必要があります。
プログラムでトランザクションを制御するために、スプリングの TransactionTemplate
を使用しないのはなぜですか?また、各「トランザクションブロック」が独自の@Transactional
メソッドを持つようにコードを再構築することもできますが、それがテストである場合、トランザクションのプログラムによる制御を選択します。
また、runnableはspringによって管理されていないため、runnableの@Transactional
アノテーションは動作しません(aspectjを使用している場合を除く)。
@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;
}
});
}
}
TransactionTemplate
のこの見苦しい匿名内部クラスの使用により、見栄えがよくないことを知っていますが、何らかの理由でトランザクションメソッドIMHOが必要な場合は flexibleオプション。
場合によっては(アプリケーションの種類によって異なります)、Springテストでトランザクションを使用する最良の方法は、テストメソッドで@Transactional
をオフにすることです。どうして? @Transactional
は多くの誤検知テストにつながる可能性があるためです。詳細を調べるには、この サンプル記事 をご覧ください。そのような場合、TransactionTemplate
は、トランザクションの境界を制御するのに最適です。