Spring Data JPA 流查询方法导致事务异常

标签 spring hibernate transactions spring-data spring-data-jpa

如果我使用返回 Stream 的 Spring Data 存储库方法,我总是得到以下异常:

org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses @Transactional or any other way of declaring a (read-only) transaction.
    org.springframework.data.jpa.repository.query.JpaQueryExecution$StreamExecution.doExecute(JpaQueryExecution.java:338)
    org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:85)
    org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:116)
    org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:106)
    org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:483)
    org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    com.sun.proxy.$Proxy201.findByPodcast(Unknown Source)
    <my controller class>$$Lambda$118/2013513791.apply(Unknown Source)

然而,有问题的代码确实应该在事务中执行。我有:
  • 用过 OpenSessionManagerInViewFilter
  • 启用声明式事务管理(在我的根上下文配置中为 @EnableTransactionManagement)并由 Controller 类和带有 @Transactional 的请求方法注释

  • 我还尝试将代码包装在 TransactionTemplate 中并在 List 中收集结果以避免交易超出范围,但这仍然没有奏效。 Controller 方法:
    @RequestMapping ( "/pod/{id}" )
    @Transactional
    public Stream<RSSPodcastItem> podItems (@PathVariable("id") UUID id)
    {
        return pods.get (id).map (items::findByPodcast).orElseThrow (() -> new RuntimeException ("failed"));
    }
    @RequestMapping ( "/podlist/{id}" )
    @Transactional
    public List<RSSPodcastItem> podItemsList (@PathVariable("id") UUID id)
    {
        return tt.execute (ts -> 
            pods.get (id).map (items::findByPodcast).orElseThrow (() -> new RuntimeException ("failed"))
            .collect (Collectors.toList()));
    }
    

    上下文根配置类:
    @Configuration
    @ComponentScan ( ... my package names ...)
    @EnableTransactionManagement
    @EnableJpaRepositories( ... package with repositories ...)
    public class SharedConfig
    {
        @Bean
        public DataSource dataSource ()
        {
             // .... snipped
        }
    
        @Bean
        EntityManagerFactory entityManagerFactory ()
        {
            LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean ();
            entityManagerFactoryBean.setDataSource (dataSource());
            entityManagerFactoryBean.setJpaVendorAdapter (new HibernateJpaVendorAdapter ());
            entityManagerFactoryBean.setPackagesToScan ( ... package with entities ...);
            entityManagerFactoryBean.setJpaPropertyMap (hibernateProperties());
            entityManagerFactoryBean.afterPropertiesSet ();
            return entityManagerFactoryBean.getObject ();
        }
    
        @Bean
        JpaTransactionManager transactionManager ()
        {
            JpaTransactionManager transactionManager = new JpaTransactionManager ();
            transactionManager.setEntityManagerFactory (entityManagerFactory());
    
            return transactionManager;
        }
    
        @Bean
        TransactionTemplate transactionTemplate (JpaTransactionManager tm)
        {
            return new TransactionTemplate (tm);
        }
    
        @Bean
        Map<String, ?> hibernateProperties ()
        {
            Map<String, Object> m = new HashMap<> ();
            m.put ("hibernate.dialect", MySQL5Dialect.class);
            m.put ("hibernate.dialect.storage_engine", "innodb");
            boolean devSystem = isDevSystem ();
            m.put ("hibernate.hbm2ddl.auto", devSystem ? "update" : "create-only");  // will need to handle updates by hand on live system, but creation is OK.
            m.put ("hibernate.show_sql", "" + devSystem);
            m.put ("hibernate.cache.use_second_level_cache", "" + !devSystem);
            return m;
        }
    

    任何建议这里出了什么问题?

    最佳答案

    按照this post :

    Repository clients can ... use the result of the method call in a try-with-resources block.



    和 Spring Data reference :

    A Stream potentially wraps underlying data store specific resources and must therefore be closed after usage. You can either manually close the Stream using the close() method or by using a Java 7 try-with-resources block.



    所以我认为你应该将你的流包装到 try-with-resource 块,并按照异常建议设置只读事务,如下所示:
    @RequestMapping("/pod/{id}")
    @Transactional(readOnly = true)
    public Stream<RSSPodcastItem> podItems (@PathVariable("id") UUID id) {
        try (Stream<RSSPodcastItem> items = repository.findByPodcast(...)) {
            return items...;
        }
    }
    

    附加信息:Spring Data - Java 8 examples

    关于Spring Data JPA 流查询方法导致事务异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45545802/

    相关文章:

    java - 没有可用于当前线程的实际事务的 EntityManager - 无法可靠地处理 'flush' 调用

    transactions - MSDTC(分布式事务协调器)首次启动缓慢

    java - 使用 Spring 和 Hessian 的互操作性?是否可以?

    java - Spring MVC框架非常基本的Dispatcher问题

    java - 在 tomcat v 8.5 上部署 spring boot 应用程序失败

    java - 有没有办法打包jar依赖项以便在独立环境中运行java类

    java - 在应用程序的生命周期中,您在哪里存储持久内存值(非数据库)?

    java - 基于Spring的应用程序无法初始化entityManagerFactory,NoSuchMethodError

    java - 如何为两个不同的 session 配置两个hibernate transactionManager

    java - 交易和发送电子邮件