Как вручную запустить транзакцию в общем EntityManager в Spring?
У меня есть LocalContainerEntityManagerFactoryBean
как EntityManager
экземпляр.
Чтобы быстро удалить содержимое полных таблиц, я хочу запустить следующий код:
@Service
public class DatabaseService {
@Autowired
private EntityManager em;
@Transactional
public void clear() {
em.createNativeQuery("TRUNCATE TABLE MyTable").executeUpdate();
}
}
Результат:
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)
Если я сделаю это изменение:
public void clear() {
em.getTransaction().begin();
em.createNativeQuery("TRUNCATE TABLE MyTable").executeUpdate();
}
Результат:
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)
Я также пробовал spring -data-jpa, но также терпит неудачу:
public interface MyRepository extends CrudRepository<MyEntity, Integer> {
@Query(value = "TRUNCATE TABLE MyTable", nativeQuery = true)
@Modifying
public void clear();
}
Итак, как я могу создать транзакцию и запустить truncate в общем контексте spring?
Приложение spring запускается с использованием:
SpringApplication.run(AppConfig.class, args);
имеющий:
@Bean
public JpaTransactionManager transactionManager() {
return new JpaTransactionManager(emf);
}
Ответы
Ответ 1
Вы должны использовать объект TransactionTemplate
для управления транзакцией:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
em.createNativeQuery("TRUNCATE TABLE MyTable).executeUpdate();
}
});
Чтобы создать TransactionTemplate, просто используйте вложенный PlatformTransactionManager
:
transactionTemplate = new TransactionTemplate(platformTransactionManager);
И если вы хотите использовать новую транзакцию, просто вызовите
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Ответ 2
В качестве обходного решения теперь я создал новый 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();
}
Это, вероятно, не идеально, но работает на данный момент.
Ответ 3
Spring Данные JPA автоматически запускают CRUD-метод в транзакциях для вас (без необходимости настраивать что-либо, кроме менеджера транзакций). Если вы хотите использовать транзакции для своих методов запросов, вы можете просто добавить @Transactional
к этим:
interface MyRepository extends CrudRepository<MyEntity, Integer> {
@Transactional
@Modifying
@Query(value = "TRUNCATE TABLE MyTable", nativeQuery = true)
void clear();
}
В более общем случае то, что вы заявили здесь, логически эквивалентно CrudRepository.deleteAll()
, за исключением того, что оно (ваша декларация) не соблюдает каскады уровня JPA. Поэтому я подумал, что ты действительно собираешься делать. Если вы используете Spring Boot, вам необходимо позаботиться о настройке активатора и менеджера транзакций.
Если вы хотите использовать @Transactional
на уровне обслуживания, вам нужно настроить как JpaTransactionManager
, так и активировать управление транзакциями на основе аннотаций через <tx:annotation-driven />
или @EnableTransactionManagement
(похоже, что активация была отсутствующей частью на ваша попытка создания транзакций на уровне сервиса).
Ответ 4
@Transactional
аннотация должна применяться не к методу Дао, а к методу сервиса. Хотя в вашем коде сказано, что DatabaseService
является службой, но вставка EntityManger
внутри службы не имеет никакого смысла.
Правильный способ реализации - создать Дао, как показано ниже.
@Repository
public class DatabaseDao {
@PersistenceContext
private EntityManager em;
public void clear() {
em.createNativeQuery("TRUNCATE TABLE MyTable").executeUpdate();
}
}
Затем вызовите метод dao из сервисного метода с аннотацией @Transactional
.
@Service
public class DatabaseService {
@Autowired
private DatabaseDao dao;
@Transactional
public void clear() {
dao.clear();
}
}
Также добавьте @EnableTransactionManagement
в свой класс Configuration