流式查询结果过早关闭 - Spring Data JPA和Hibernate

Ale*_*nov 7 hibernate spring-data spring-data-jpa java-stream

这是一个存储库,其中包含此问题中的代码,其中包含错误:https: //github.com/agsimeonov/stream-bug

我一直在尝试使用以下代码片段使用Spring Data JPA和Hibernate来传输查询结果(data.txt是一个3000行的文件,每行都有一个数字):

try (Stream<Customer> stream = repository.streamAll()) {
  stream.forEach(customer -> {
    try {
      File data = new File(getClass().getClassLoader().getResource("data.txt").getFile());
      try (BufferedReader reader = new BufferedReader(new FileReader(data))) {
        while (reader.readLine() != null) {
          // Do stuff for the current customer
        }
      }
    } catch (IOException e) {}
    System.out.println(customer);
  });
}
Run Code Online (Sandbox Code Playgroud)

这是域对象:

@Entity
@Table(name = "customer")
public class Customer {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String firstName;
  private String lastName;

  public Customer() {}

  public Customer(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
  }
}
Run Code Online (Sandbox Code Playgroud)

这是存储库:

public interface CustomerRepository extends JpaRepository<Customer, Long> {
  @Query("SELECT c FROM Customer c")
  Stream<Customer> streamAll();
}
Run Code Online (Sandbox Code Playgroud)

执行此操作会导致以下错误:

org.hibernate.exception.GenericJDBCException: could not advance using next()
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)
    at org.hibernate.internal.ScrollableResultsImpl.convert(ScrollableResultsImpl.java:69)
    at org.hibernate.internal.ScrollableResultsImpl.next(ScrollableResultsImpl.java:104)
    at org.springframework.data.jpa.provider.PersistenceProvider$HibernateScrollableResultsIterator.hasNext(PersistenceProvider.java:454)
    at java.util.Iterator.forEachRemaining(Iterator.java:115)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at stream.bug.StreamBugApplication.lambda$0(StreamBugApplication.java:34)
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800)
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:784)
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:771)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1186)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1175)
    at stream.bug.StreamBugApplication.main(StreamBugApplication.java:22)
Caused by: org.h2.jdbc.JdbcSQLException: The object is already closed [90007-193]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
    at org.h2.message.DbException.get(DbException.java:179)
    at org.h2.message.DbException.get(DbException.java:155)
    at org.h2.message.DbException.get(DbException.java:144)
    at org.h2.jdbc.JdbcResultSet.checkClosed(JdbcResultSet.java:3202)
    at org.h2.jdbc.JdbcResultSet.next(JdbcResultSet.java:129)
    at org.hibernate.internal.ScrollableResultsImpl.next(ScrollableResultsImpl.java:99)
    ... 12 more
Run Code Online (Sandbox Code Playgroud)

我花了很多时间调试这个,我终于设法创建了一个小的spring-boot示例应用程序,展示了流错误:https: //github.com/agsimeonov/stream-bug

我肯定知道一些事情:

首先 - 这个bug与底层数据库无关.虽然我在示例项目中使用H2,但我尝试使用Postgres,但是仍然会出现一个非常类似的错误,请注意我在其他项目中使用tomcat连接池,我尝试过不同的连接池,所以它绝对不是连接池或导致此问题的底层数据库.这是一个带有postgres和tomcat连接池的示例跟踪,您可能会注意到它非常相似:

org.hibernate.exception.GenericJDBCException: could not advance using next()
  at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
  at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
  at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
  at org.hibernate.internal.ScrollableResultsImpl.convert(ScrollableResultsImpl.java:69)
  at org.hibernate.internal.ScrollableResultsImpl.next(ScrollableResultsImpl.java:104)
  at org.springframework.data.jpa.provider.PersistenceProvider$HibernateScrollableResultsIterator.hasNext(PersistenceProvider.java:454)
  at java.util.Iterator.forEachRemaining(Iterator.java:115)
  at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
  at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
  at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
  at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
  at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
  at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
  at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
  at com.trove.sunstone.attributefusion.services.impl.PhysicalServiceImpl.match(PhysicalServiceImpl.java:130)
  at com.trove.sunstone.attributefusion.AppRunner.main(AppRunner.java:31)
  Suppressed: java.lang.reflect.UndeclaredThrowableException
    at com.sun.proxy.$Proxy238.hashCode(Unknown Source)
    at java.util.HashMap.hash(HashMap.java:338)
    at java.util.HashMap.get(HashMap.java:556)
    at org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl.release(ResourceRegistryStandardImpl.java:76)
    at org.hibernate.internal.AbstractScrollableResults.close(AbstractScrollableResults.java:104)
    at org.springframework.data.jpa.provider.PersistenceProvider$HibernateScrollableResultsIterator.close(PersistenceProvider.java:465)
    at org.springframework.data.util.StreamUtils$CloseableIteratorDisposingRunnable.run(StreamUtils.java:96)
    at java.util.stream.AbstractPipeline.close(AbstractPipeline.java:323)
    at com.trove.sunstone.attributefusion.services.impl.PhysicalServiceImpl.match(PhysicalServiceImpl.java:137)
    ... 1 more
  Caused by: java.sql.SQLException: Statement closed.
    at org.apache.tomcat.jdbc.pool.interceptor.AbstractQueryReport$StatementProxy.invoke(AbstractQueryReport.java:224)
    ... 10 more
Caused by: org.postgresql.util.PSQLException: This ResultSet is closed.
  at org.postgresql.jdbc.PgResultSet.checkClosed(PgResultSet.java:2740)
  at org.postgresql.jdbc.PgResultSet.next(PgResultSet.java:1817)
  at org.hibernate.internal.ScrollableResultsImpl.next(ScrollableResultsImpl.java:99)
  ... 11 more
Run Code Online (Sandbox Code Playgroud)

第二个 - 奇怪的是,从流中的forEach()中删除以下行会导致流正确完成.这让我相信它可能是某种时间问题,但是我试图用Thread.sleep()而不是文件读取来复制它而没有成功.作为旁注,data.txt是一个包含3000行的文件,每行都有一个数字.

try {
  File data = new File(getClass().getClassLoader().getResource("data.txt").getFile());
  try (BufferedReader reader = new BufferedReader(new FileReader(data))) {
    while (reader.readLine() != null) {
      // Do stuff for the current customer
    }
  }
} catch (IOException e) {}
Run Code Online (Sandbox Code Playgroud)

第三 - 更换:

Stream<Customer> stream = repository.streamAll()
Run Code Online (Sandbox Code Playgroud)

附:

Stream<Customer> stream = repository.findAll().stream()
Run Code Online (Sandbox Code Playgroud)

修复了这个问题所以这肯定是流和/或ScrollableResults的错误,因为将所有数据加载到列表中会使应用程序完成没有错误,但是对于我当前的项目,我需要直接使用Streams,因此使用findAll()不是选项.

如果有人遇到此问题并能够修复它,请告诉我.另外,请随时检查,分叉和/或更改提供的存储库中的代码,这有助于解决此问题.我已经创建了这个项目作为演示,应该用来说明这个bug.

Ale*_*nov 9

我在Spring Data JPA JIRA上发布了我的问题作为错误报告,之前显然已经发现了这个问题.经过一些讨论后,我现在在Stream相关代码上使用@Transactional来解决问题作为一种解决方法.感谢Oliver Gierke在此指出:https://jira.spring.io/browse/DATAJPA-989?focusCommentId = 133710&page = com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-133710

我已经将解决​​方案推送到最近提交到我的示例错误存储库中的错误:https: //github.com/agsimeonov/stream-bug/commit/9da536d0a9d921787f6d2d4d75720d363ba0358b