¿Cómo forzar manualmente una confirmación en un método @Transactional?

Estoy usando Spring / Spring-data-JPA y me veo en la necesidad de forzar manualmente una confirmación en una prueba unitaria. Mi caso de uso es que estoy haciendo una prueba de subprocesos múltiples en la que tengo que usar datos que persisten antes de que se generen los subprocesos.

Desafortunadamente, dado que la prueba se está ejecutando en una transacción @Transactional , incluso una flush no la hace accesible a los subprocesos generados.

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

Intenté usar el administrador de entidades, pero recibo un mensaje de error cuando lo hago:

 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) 

¿Hay alguna forma de comprometer la transacción y continuarla? No he podido encontrar ningún método que me permita llamar a commit() .

Tuve un caso de uso similar durante la prueba de detectores de eventos de hibernación que solo se invocan en commit.

La solución fue ajustar el código para que sea persistente en otro método anotado con REQUIRES_NEW . (En otra clase) De esta forma se genera una nueva transacción y se emite un flush / commit una vez que el método retorna.

Tx prop REQUIERES_NEW

Tenga en cuenta que esto puede influir en todas las otras pruebas. Así que escríbalos en consecuencia o debe asegurarse de que pueda limpiar después de que se ejecutó la prueba.

¿Por qué no utilizas la TransactionTemplate de TransactionTemplate de Spring para controlar las transacciones mediante progtwigción? También podría reestructurar su código para que cada “bloque de transacción” tenga su propio método @Transactional , pero dado que es una prueba, optaría por el control programático de sus transacciones.

También tenga en cuenta que la anotación @Transactional en su ejecutable no funcionará (a menos que esté utilizando aspectj) ya que los ejecutables no se gestionan antes de la 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() { @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() { @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() { @Override public Object doInTransaction(TransactionStatus status) { // validate test results in transaction return null; } }); } 

}

Sé que debido a este feo uso anónimo de clase interna de TransactionTemplate no se ve bien, pero cuando por alguna razón queremos tener un método de prueba transaccional en mi humilde opinión, es la opción más flexible .

En algunos casos (depende del tipo de aplicación), la mejor forma de usar transacciones en las pruebas de Spring es un @Transactional desactivado en los métodos de prueba. ¿Por qué? Debido a que @Transactional puede conducir a muchas pruebas falsas positivas. Puede mirar este artículo de muestra para conocer detalles. En tales casos, TransactionTemplate puede ser perfecto para controlar límites de transacción cuando queremos ese control.