java - management - spring transaction commit and rollback
¿Cómo forzar manualmente una confirmación en un método @Transactional? (3)
Esta pregunta ya tiene una respuesta aquí:
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<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());
}
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()
.
¿Por qué no utilizas la TransactionTemplate
de TransactionTemplate
de Spring para controlar las transacciones mediante programació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 primavera.
@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;
}
});
}
}
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.
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.
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.