Spring boot + Hibernate + JPA Не доступен транзакционный EntityManager

Я использую spring boot 1.2.3.RELEASE версию с JPA за спящий режим. Я испытываю следующее исключение

org.springframework.dao.InvalidDataAccessApiUsageException: No transactional EntityManager available; nested exception is javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:410) ~[EntityManagerFactoryUtils.class:4.1.6.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223) ~[HibernateJpaDialect.class:4.1.6.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417) ~[AbstractEntityManagerFactoryBean.class:4.1.6.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) ~[ChainedPersistenceExceptionTranslator.class:4.1.6.RELEASE]
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) ~[DataAccessUtils.class:4.1.6.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) ~[PersistenceExceptionTranslationInterceptor.class:4.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:122) ~[CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.class:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) [ExposeInvocationInterceptor.class:4.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [ReflectiveMethodInvocation.class:4.1.6.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) [JdkDynamicAopProxy.class:4.1.6.RELEASE]
at com.sun.proxy.$Proxy110.deleteByCustomerId(Unknown Source) ~[na:na]

Caused by: javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:275) ~[SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.class:4.1.6.RELEASE]
at com.sun.proxy.$Proxy102.remove(Unknown Source) ~[na:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$DeleteExecution.doExecute(JpaQueryExecution.java:270) ~[JpaQueryExecution$DeleteExecution.class:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:74) ~[JpaQueryExecution.class:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:97) ~[AbstractJpaQuery.class:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:88) ~[AbstractJpaQuery.class:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:395) ~[RepositoryFactorySupport$QueryExecutorMethodInterceptor.class:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:373) ~[RepositoryFactorySupport$QueryExecutorMethodInterceptor.class:na]

Ниже приведена моя структура программы
Класс конфигурации

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableTransactionManagement
public class WSApplication {
    public static void main(final String[] args) {
        SpringApplication.run(WSApplication.class, args);
    }
}

@Entity
@Table(Orders)
public class Order {
    @id
    @GeneratedValue
    private long id;

    @Column(name = "customerId")
    private Long customerId;

    // getter & setter methods
    // equals & hashCode methods
}

public interface OrderRepository extends JpaRepository<Order, Long> {

    List<Order> findByCustomerId(Long customerId);

    // 4- @Transactional works fine
    void deleteByCustomerId(Long cusotmerId);

}

public class OrderService {

    @Autowired
    private OrderRepository repo;

    // 3- @Transactional works fine
    public void deleteOrder(long customerId){
        //1- throws exception
        repo.deleteByCustomerId(customerId); 

        //2- following works fine
        //repo.delete(repo.findByCustomerId(customerId).get(0));
    }

}

В вышеприведенном коде класса обслуживания, кто-нибудь может направить меня, почему 2 работает и исключение 1 бросает.

Спасибо

Ответ 1

Во-первых, я делаю цитату Spring -Data JPA Documentation, чтобы оправдать, почему метод delete работает в вашем случае (Я имею в виду опцию 2).

Методы CRUD для экземпляров репозитория по умолчанию являются транзакционными. Для операции чтения, установлен флаг конфигурации транзакции readOnlyдля true, все остальные настроены с помощью простого @Transactional, так что применяется конфигурация транзакции по умолчанию. Подробнее см. В JavaDoc CrudRepository

Метод delete на самом деле является методом CrudRepository. Ваш репозиторий расширяет JpaRepository, который расширяет CrudRespository, поэтому он принадлежит интерфейсу CrudRepository и в соответствии с приведенной выше транзакцией является транзакционным.

Если вы прочитали раздел Метод транзакционных запросов, вы увидите, что это то же самое, что опция 4 и вы узнаете, как применять пользовательское транзакционное поведение для всех методов вашего репозитория. Кроме того, в примере 61 документации показан тот же сценарий, что и параметр 3.

Теперь имейте в виду, что вы не работаете с логикой JDBC, и в этом случае база данных заботится о транзакциях, но в рамках ORM. Структуры ORM требуют транзакции, чтобы инициировать синхронизацию между кешем объекта и базой данных. Поэтому вы должны знать и предоставлять контекст транзакции для методов, которые выполняют логику ORM, например deleteByCustomerId.

По умолчанию @Transactional (я имею в виду без каких-либо параметров) установите режим распространения на REQUIRED и флаг readOnly на false. Когда вы вызываете метод, аннотированный внутри, транзакция является intialized, если никто не существует. Именно по этой причине работает обходной путь @LucasSaldanha (такой же, как пример с использованием фасада для определения транзакций для нескольких вызовов репозитория) и опция 4. В другом случае, без транзакции, вы попадаете в исключенную опцию 1.

Ответ 2

Хорошо, я выяснил, как это работает.

Просто добавьте аннотацию @Transactional (org.springframework.transaction.annotation.Transactional) в свой метод deleteOrder в OrderService.

@Transactional
public void deleteOrder(long customerId){
    repo.deleteByCustomerId(customerId);
}

Я действительно не знаю, почему второй работает. Я предполагаю, что, поскольку это прямой метод из интерфейса CrudRepository, он знает, как его выполнить атомарно.

Первый - вызов deleteByCustomerId. Этот вызов будет обработан, чтобы узнать клиента с указанным идентификатором, а затем удалит его. По какой-то причине он использует явную транзакцию.

Опять же, это просто догадка. Я попытаюсь связаться с некоторыми разработчиками spring и, возможно, открыть проблему, чтобы проверить это поведение.

Надеюсь, что это поможет!

Ссылка: http://spring.io/guides/gs/managing-transactions/