Spring Boot + Hibernate + JPA:没有可用的事务性EntityManager

16

我正在使用Spring Boot 1.2.3.RELEASE版本,结合Hibernate的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会抛出异常吗?

谢谢

4个回答

16

首先,我引用Spring-Data JPA Documentation的内容来证明为什么在您的情况下(指选项2)delete方法有效。

存储库实例上的CRUD方法默认情况下是事务性的。对于读取操作,将readOnly标志设置为true,所有其他操作均配置为普通的@Transactional,以便应用默认事务配置。有关详细信息,请参阅CrudRepository的JavaDoc。

delete方法实际上是CrudRepository的一个方法。您的存储库扩展了JpaRepository,它又扩展了CrudRespository,因此它属于CrudRepository接口,并根据上述引用是事务性的。

如果您阅读事务查询方法一节,您将会发现它与选项4相同,并且您将知道如何为存储库的所有方法应用自定义事务行为。此外,文档中的示例61展示了与选项3相同的场景。
请注意,您不是在使用JDBC逻辑,因为在这种情况下,数据库会处理事务,而是在ORM框架内工作。ORM框架需要一个事务来触发对象缓存和数据库之间的同步。
因此,您必须意识到并为执行ORM逻辑的方法(例如deleteByCustomerId)提供事务上下文。
默认情况下,@Transactional(指没有任何参数)将传播模式设置为REQUIRED,只读标志设置为false。当您调用其中注释的方法时,如果不存在事务,则会初始化一个事务。这就是为什么@LucasSaldanha的解决方法(与示例使用外观为多个存储库调用定义事务相同)和选项4有效的原因。否则,如果没有事务,您将遇到选项1抛出的异常。

文档应该比现在更详细地提到这一点。感谢您的帮助。 - Nikhil Sahu
我猜你指的是_Example 81_而不是_Example 61_。 - KLaalo

3

好的,我发现了一种使它工作的方法。

只需要在你的OrderService中的deleteOrder方法中加入@Transactional注解(org.springframework.transaction.annotation.Transactional)即可。

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

我真的不知道第二个方法为什么有效。我猜测,由于它是从CrudRepository接口的直接方法,所以它以某种方式知道如何以原子方式执行它。
前者是对deleteByCustomerId的调用。此调用将被处理以查找具有指定ID的客户,并删除该客户。出于某种原因,它使用了显式事务。
再次强调,这只是一个猜测。我将尝试联系一些Spring开发人员,可能会打开一个问题来验证这种行为。
希望它有所帮助!
参考:http://spring.io/guides/gs/managing-transactions/

谢谢,但我更想知道为什么deleteByCustomerId不起作用。据我所知,Spring应该自动启动事务,就像它对delete方法所做的那样。 - amique
为什么会这样,只有超级方法是事务性的,如果你想让你的方法成为事务性的,你必须将其标记为事务性。此外,答案实际上是正确的(在我看来),因为你的事务边界应该是服务调用而不是dao调用。 - M. Deinum

3

即使我在search()方法上使用了@Transactional注解,仍然出现了No transactional EntityManager available异常。

我按照这篇教程配置了Spring Boot中的Hibernate搜索。

问题在于我使用了不同的hibernate-search-orm依赖项。对我而言没有任何问题的依赖项是:

compile("org.hibernate:hibernate-search-orm:5.7.0.Final")

在将此内容添加到gradle构建文件后,一切都按预期工作。
希望这也能帮助其他人。

2
这个对我起作用了,我之前使用的是5.11.0.Final版本,降级到5.7.0.Final版本后它就可以工作了。 - Raffaeu

0

SimpleJpaRepository类是Spring用于为存储库接口提供实现的默认类。如果您检查该类,您可以看到被调用的函数具有@Transactional注释。

@Override
    @Transactional
    public void deleteAllById(Iterable<? extends ID> ids) {

        Assert.notNull(ids, "Ids must not be null!");

        for (ID id : ids) {
            deleteById(id);
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接