J'ai une instance LocalContainerEntityManagerFactoryBean
en tant qu'instance EntityManager
.
Pour supprimer rapidement le contenu d'un tableau complet, je souhaite exécuter le code suivant:
@Service
public class DatabaseService {
@Autowired
private EntityManager em;
@Transactional
public void clear() {
em.createNativeQuery("TRUNCATE TABLE MyTable").executeUpdate();
}
}
Résultat:
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)
Si je fais ce changement:
public void clear() {
em.getTransaction().begin();
em.createNativeQuery("TRUNCATE TABLE MyTable").executeUpdate();
}
Résultat:
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)
J'ai aussi essayé spring-data-jpa, mais j'ai aussi échoué:
public interface MyRepository extends CrudRepository<MyEntity, Integer> {
@Query(value = "TRUNCATE TABLE MyTable", nativeQuery = true)
@Modifying
public void clear();
}
Alors, comment puis-je créer une transaction et exécuter le tronquer dans un contexte de ressort partagé?
L'application Spring est lancée avec: SpringApplication.run(AppConfig.class, args);
ayant:
@Bean
public JpaTransactionManager transactionManager() {
return new JpaTransactionManager(emf);
}
Vous devez utiliser l'objet TransactionTemplate
pour gérer la transaction de manière impérative:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
em.createNativeQuery("TRUNCATE TABLE MyTable).executeUpdate();
}
});
Pour créer TransactionTemplate, utilisez simplement la variable PlatformTransactionManager
injectée:
transactionTemplate = new TransactionTemplate(platformTransactionManager);
Et si vous voulez utiliser une nouvelle transaction, appelez simplement
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
En guise de solution de contournement, j'ai maintenant créé une nouvelle variable EntityManager
à l'aide de la variable EMF
et démarrant la transaction manuellement.
@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();
}
Ce n'est probablement pas idéal, mais fonctionne pour le moment.
Spring Data JPA exécute automatiquement la méthode CRUD dans les transactions pour vous (sans rien configurer à part un gestionnaire de transaction). Si vous souhaitez utiliser des transactions pour vos méthodes de requête, vous pouvez simplement ajouter @Transactional
à celles-ci:
interface MyRepository extends CrudRepository<MyEntity, Integer> {
@Transactional
@Modifying
@Query(value = "TRUNCATE TABLE MyTable", nativeQuery = true)
void clear();
}
Sur une note plus générale, ce que vous avez déclaré ici est logiquement équivalent à CrudRepository.deleteAll()
, à la différence près qu'il (votre déclaration) ne respecte pas les cascades de niveau JPA. Je me suis donc demandé si c'était vraiment ce que vous aviez l'intention de faire. Si vous utilisez Spring Boot, la configuration de l'activation et du gestionnaire de transactions doit être prise en charge pour vous.
Si vous souhaitez utiliser @Transactional
au niveau de service, vous devez configurer à la fois une JpaTransactionManager
et activer la gestion des transactions basée sur des annotations via <tx:annotation-driven />
ou @EnableTransactionManagement
(l’activation semble être l’élément manquant de votre tentative de création de transactions le couche de service).
L'annotation @Transactional
ne doit pas être appliquée à la méthode Dao mais à une méthode de service. Bien que votre code indique que DatabaseService
est un service, mais insérer EntityManger
dans un service n'a aucun sens.
La manière correcte de mettre en œuvre est de créer un Dao comme ci-dessous.
@Repository
public class DatabaseDao {
@PersistenceContext
private EntityManager em;
public void clear() {
em.createNativeQuery("TRUNCATE TABLE MyTable").executeUpdate();
}
}
Appelez ensuite la méthode dao depuis une méthode de service avec l'annotation @Transactional
.
@Service
public class DatabaseService {
@Autowired
private DatabaseDao dao;
@Transactional
public void clear() {
dao.clear();
}
}
Ajoutez également @EnableTransactionManagement
dans votre classe Configuration