Como iniciar manualmente uma transação em um EntityManager compartilhado no Spring?

Eu tenho uma instância LocalContainerEntityManagerFactoryBean como EntityManager .

Para descartar rapidamente o conteúdo de uma tabela completa, quero executar o seguinte código:

 @Service public class DatabaseService { @Autowired private EntityManager em; @Transactional public void clear() { em.createNativeQuery("TRUNCATE TABLE MyTable").executeUpdate(); } } 

Resultado:

 ERROR org.springframework.integration.handler.LoggingHandler: javax.persistence.TransactionRequiredException: Executing an update/delete query at org.hibernate.jpa.spi.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:71) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:708) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:304) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744) 

Se eu fizer essa alteração:

 public void clear() { em.getTransaction().begin(); em.createNativeQuery("TRUNCATE TABLE MyTable").executeUpdate(); } 

Resultado:

 ERROR org.springframework.integration.handler.LoggingHandler: java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:245) at com.sun.proxy.$Proxy84.getTransaction(Unknown Source) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:708) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:304) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744) 

Eu também tentei spring-data-jpa, mas também falha:

 public interface MyRepository extends CrudRepository { @Query(value = "TRUNCATE TABLE MyTable", nativeQuery = true) @Modifying public void clear(); } 

Então, como posso criar uma transação e executar o truncamento em um contexto de mola compartilhado?

O aplicativo Spring é iniciado usando: SpringApplication.run(AppConfig.class, args); tendo:

 @Bean public JpaTransactionManager transactionManager() { return new JpaTransactionManager(emf); } 

Você deve usar o object TransactionTemplate para gerenciar a transação de forma imperativa:

 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { em.createNativeQuery("TRUNCATE TABLE MyTable).executeUpdate(); } }); 

Para criar o TransactionTemplate, basta usar o PlatformTransactionManager injetado:

 transactionTemplate = new TransactionTemplate(platformTransactionManager); 

E se você quiser usar uma nova transação, invoque

 transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); 

Como solução alternativa, criei agora um novo EntityManager explícito usando o EMF e iniciando a transação manualmente.

 @Autowired private EntityManagerFactory emf; public void clearTable() { EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); em.createNativeQuery("TRUNCATE TABLE MyTable).executeUpdate(); tx.commit(); em.close(); } 

Isso provavelmente não é o ideal, mas funciona no momento.

Spring Data A JPA executa automaticamente o método CRUD em transactions para você (sem precisar configurar nada, exceto um gerenciador de transactions). Se você quiser usar transactions para seus methods de consulta, basta adicionar @Transactional a estes:

 interface MyRepository extends CrudRepository { @Transactional @Modifying @Query(value = "TRUNCATE TABLE MyTable", nativeQuery = true) void clear(); } 

Em uma nota mais geral, o que você declarou aqui é logicamente equivalente a CrudRepository.deleteAll() , exceto que (sua declaração) não honra cascatas no nível do JPA. Então eu me perguntei o que você pretendia fazer. Se você estiver usando o Spring Boot, a configuração do gerenciador de transactions e ativação deve ser feita para você.

Se você quiser usar @Transactional no nível de serviço, precisará configurar um JpaTransactionManager e ativar o gerenciamento de transactions baseado em annotations por meio de ou @EnableTransactionManagement (parece que a ativação foi a peça que faltava em sua tentativa para criar transactions na camada de serviço).