Java 如何在 Spring 中的共享 EntityManager 上手动启动事务?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26606608/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 02:54:39  来源:igfitidea点击:

How to manually start a transaction on a shared EntityManager in Spring?

javaspringspring-data-jpaentitymanager

提问by membersound

I have a LocalContainerEntityManagerFactoryBeanas EntityManagerinstance.

我有一个LocalContainerEntityManagerFactoryBean作为EntityManager实例。

To quickly drop a full tables' content, I want to run the following code:

要快速删除完整表格的内容,我想运行以下代码:

@Service
public class DatabaseService {
    @Autowired
    private EntityManager em;

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

Result:

结果:

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

If I make this change:

如果我进行此更改:

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

Result:

结果:

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

I also tried spring-data-jpa, but also fails:

我也试过 spring-data-jpa,但也失败了:

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

So, how can I create a transaction and run the truncate in a shared spring context?

那么,如何创建事务并在共享的 spring 上下文中运行截断?

The Spring application is started using: SpringApplication.run(AppConfig.class, args);having:

Spring 应用程序启动时使用: SpringApplication.run(AppConfig.class, args);具有:

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

采纳答案by Jakub Kubrynski

You should use TransactionTemplateobject to manage transaction imperatively:

您应该使用TransactionTemplateobject 来强制管理事务:

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

To create TransactionTemplate just use injected PlatformTransactionManager:

要创建 TransactionTemplate 只需使用注入PlatformTransactionManager

transactionTemplate = new TransactionTemplate(platformTransactionManager);

And if you want to use new transaction just invoke

如果您想使用新交易,只需调用

transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

回答by membersound

As a workaround I now created a new EntityManagerexplicit using the EMF, and starting the transaction manually.

作为一种解决方法,我现在EntityManager使用EMF,创建了一个新的显式,并手动启动事务。

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

That's probably not ideal, but works for the moment.

这可能并不理想,但目前有效。

回答by Oliver Drotbohm

Spring Data JPA automatically runs CRUD method in transactions for you (without needing to set up anything except a transaction manager). If you want to use transactions for your query methods, you can simply add @Transactionalto these:

Spring Data JPA 会自动为您在事务中运行 CRUD 方法(无需设置除事务管理器以外的任何内容)。如果你想在查询方法中使用事务,你可以简单地添加@Transactional到这些:

interface MyRepository extends CrudRepository<MyEntity, Integer> {

  @Transactional
  @Modifying
  @Query(value = "TRUNCATE TABLE MyTable", nativeQuery = true)
  void clear();
}

On a more general note, what you have declared here is logically equivalent to CrudRepository.deleteAll(), except that it (your declaration) doesn't honor JPA-level cascades. So I wondered that's really what you intended to do. If you're using Spring Boot, the activation and transaction manager setup should be taken care of for you.

更一般地说,您在此处声明的内容在逻辑上等同于CrudRepository.deleteAll(),只是它(您的声明)不支持 JPA 级级联。所以我想知道这真的是你打算做的。如果您使用的是 Spring Boot,则应该为您处理激活和事务管理器设置。

If you want to use @Transactionalon the service level, you need to setup both a JpaTransactionManagerandactivate annotation based transaction management through either <tx:annotation-driven />or @EnableTransactionManagement(looks like the activation was the missing piece on your attempt to create transactions on the service layer).

如果您想@Transactional在服务级别使用,则需要通过或(看起来激活是您尝试在服务层上创建事务时缺少的部分)来设置 aJpaTransactionManageractivate 基于注释的事务管理。<tx:annotation-driven />@EnableTransactionManagement

回答by Manish Bansal

@Transactionalannotation should not be applied on Dao method but on a service method. Although your code says DatabaseServiceis a service, but by inserting EntityMangerinside a service does not make any sense.

@Transactional注解不应应用于 Dao 方法,而应应用于服务方法。虽然你的代码说的DatabaseService是一个服务,但是EntityManger在服务里面插入是没有任何意义的。

Correct way to implement is to create a Dao like below.

正确的实现方法是创建一个如下所示的 Dao。

@Repository
public class DatabaseDao {
    @PersistenceContext
    private EntityManager em;

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

Then call the dao method from a service method with @Transactionalannotation.

然后从带有@Transactional注解的服务方法中调用 dao 方法。

@Service
public class DatabaseService {
    @Autowired
    private DatabaseDao dao;

    @Transactional
    public void clear() {
        dao.clear();
    }
}

Also, add @EnableTransactionManagementin your Configurationclass

另外,@EnableTransactionManagement在您的Configuration班级中添加