【问题标题】:Spring Data JPA streaming query methods cause an exception about transactionsSpring Data JPA 流式查询方法导致事务异常
【发布时间】:2018-01-14 16:39:58
【问题描述】:

如果我使用返回 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 在我的根上下文配置中)并由控制器类使用@Transactional 的请求方法进行注释

我还尝试将代码包装在 TransactionTemplate 中并将结果收集在 List 中以避免事务超出范围,但这仍然没有奏效。控制器方法:

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

有什么建议吗?

【问题讨论】:

  • 我的回答对你有帮助吗?..

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


【解决方案1】:

按照this post

存储库客户端可以...在 try-with-resources 块中使用方法调用的结果。

还有 Spring Data reference:

流可能会包装底层数据存储特定资源,因此必须在使用后关闭。您可以使用 close() 方法或使用 Java 7 try-with-resources 块手动关闭 Stream。

所以我认为你应该将你的流包装到 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

【讨论】:

  • 如果在我返回之前关闭了流,MVC 应该如何使用它?
  • @Jules 我认为它应该在 Spring 5 - stackoverflow.com/a/44509954 ((
  • Spring Data - Java 8 示例链接已损坏。
【解决方案2】:

这对我有用:

// 存储库:

@Query("HQL Query here")
Stream<Object[]> findEntities();

// 服务:

@Autowired
private PlatformTransactionManager transactionManager;

@Autowired
private EntityRepository entityRepository;

.....

TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
       try (Stream<Object[]> entityStream =entityRepository.findEntities()) {
        entityStream.forEach(entityObjs -> {
            // Here deal with each entity as entityObjs
        });
    }
  }
);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-04
    • 2019-04-16
    • 2017-01-29
    • 2015-04-20
    • 2014-12-11
    • 2013-11-13
    • 1970-01-01
    • 2015-06-18
    相关资源
    最近更新 更多