web-dev-qa-db-fra.com

Comment démarrer manuellement une transaction sur un EntityManager partagé au printemps?

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);
}
22
membersound

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);
37
Jakub Kubrynski

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.

11
membersound

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).

7
Oliver Drotbohm

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

0
Manish Bansal