这里是一个包含本问题代码并展示了错误的存储库:https://github.com/agsimeonov/stream-bug
我一直在尝试使用以下代码片段(data.txt是一个每行都有一个数字的3000行文件)通过Spring Data JPA和Hibernate来流式查询结果:
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);
});
}
这里是领域对象:
@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);
}
}
这里是代码库:
public interface CustomerRepository extends JpaRepository<Customer, Long> {
@Query("SELECT c FROM Customer c")
Stream<Customer> streamAll();
}
这样做会导致以下错误:
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
我花了很多时间进行调试,最终成功创建了一个小的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
第二点 - 奇怪的是,如果从stream中的forEach()方法中删除以下代码行,则会导致stream正常结束。这让我相信可能存在某种时间问题,但是我尝试使用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) {}
第三步 - 替换:
Stream<Customer> stream = repository.streamAll()
使用:
Stream<Customer> stream = repository.findAll().stream()
修复了这个问题,所以这明显是流和/或ScrollableResults的一个bug,将所有数据加载到列表中可以使应用程序无错误地完成,但是对于我的当前项目,我需要直接使用Streams,因此使用findAll()不是一个选项。
如果有人遇到过这个问题并且已经能够解决,请告诉我。另外,请随意查看、分叉和/或更改提供的存储库中的代码,这可帮助解决此问题。我创建了这个项目作为演示,应该用来说明这个错误。
catch(IOException ex){}
,它会默默地忽略异常,并且由于手动关闭,读取器在异常情况下不会关闭,因此您不会注意到后续错误,例如打开的文件句柄过多。因此,编写一个干净的示例足以证明这不是问题将是非常有帮助的。 - Holger