大量のデータ(約1億レコード)を持つテーブルはほとんどありません。したがって、このデータをメモリに保存することはできませんが、Java.util.stream
_クラスを使用してこのresult setをストリーミングし、このストリームを別のストリームに渡したいと思いますクラス。 _Stream.of
_および_Stream.Builder
_演算子について読みましたが、これらはメモリ内のバッファリングされたストリームです。この質問を解決する方法はありますか?前もって感謝します。
更新#1
さてグーグルで検索してjooqライブラリを見つけました。よくわかりませんが、テストケースに適用できるようです。要約すると、大量のデータを持つテーブルはほとんどありません。結果セットをストリーミングし、このストリームを別のメソッドに転送したいと思います。このようなもの:
_// why return Stream<String>? Because my result set has String type
private Stream<Record> writeTableToStream(DataSource dataSource, String table) {
Stream<Record> record = null;
try (Connection connection = dataSource.getConnection()) {
String sql = "select * from " + table;
try (PreparedStatement pSt = connection.prepareStatement(sql)) {
connection.setAutoCommit(false);
pSt.setFetchSize(5000);
ResultSet resultSet = pSt.executeQuery();
//
record = DSL.using(connection)
.fetch(resultSet).stream();
}
} catch (SQLException sqlEx) {
logger.error(sqlEx);
}
return record;
}
_
誰かアドバイスしてください、私は正しい方法ですか?ありがとう。
更新#2
jooqでいくつかの実験を行ったところ、上記の決定は私には適さないと言えるようになりました。このコードrecord = DSL.using(connection).fetch(resultSet).stream();
は時間がかかりすぎる
最初に理解する必要があるのは、次のようなコードです
_try (Connection connection = dataSource.getConnection()) {
…
try (PreparedStatement pSt = connection.prepareStatement(sql)) {
…
return stream;
}
}
_
try
ブロックを離れる時点では機能しません。リソースは閉じられ、Stream
の処理も開始されていません。
リソース管理構造「リソースで試す」は、メソッド内のブロックスコープ内で使用されるリソースに対して機能しますが、リソースを返すファクトリメソッドを作成しています。したがって、返されたストリームを閉じるとリソースが閉じられ、呼び出し元がStream
を閉じることを保証する必要があります。
さらに、ResultSet
からの単一行から項目を生成する関数が必要です。のような方法があるとします
_Record createRecord(ResultSet rs) {
…
}
_
基本的に次のような_Stream<Record>
_を作成できます
_Stream<Record> stream = StreamSupport.stream(new Spliterators.AbstractSpliterator<Record>(
Long.MAX_VALUE,Spliterator.ORDERED) {
@Override
public boolean tryAdvance(Consumer<? super Record> action) {
if(!resultSet.next()) return false;
action.accept(createRecord(resultSet));
return true;
}
}, false);
_
しかし、それを正しく行うには、例外処理とリソースのクローズを組み込む必要があります。 _Stream.onClose
_を使用して、Stream
が閉じられたときに実行されるアクションを登録できますが、チェック例外をスローできないRunnable
である必要があります。同様に、tryAdvance
メソッドはチェック済み例外をスローできません。また、ここでtry(…)
ブロックを単純にネストすることはできないため、保留中の例外がある場合にclose
でスローされる抑制例外のプログラムロジックは無料では提供されません。
ここで役立つように、チェック例外をスローし、未チェック例外にラップして配信する可能性のあるクローズ操作をラップできる新しいタイプを導入します。 AutoCloseable
自体を実装することにより、try(…)
コンストラクトを使用して、クローズ操作を安全にチェーンできます。
_interface UncheckedCloseable extends Runnable, AutoCloseable {
default void run() {
try { close(); } catch(Exception ex) { throw new RuntimeException(ex); }
}
static UncheckedCloseable wrap(AutoCloseable c) {
return c::close;
}
default UncheckedCloseable nest(AutoCloseable c) {
return ()->{ try(UncheckedCloseable c1=this) { c.close(); } };
}
}
_
これにより、操作全体が次のようになります。
_private Stream<Record> tableAsStream(DataSource dataSource, String table)
throws SQLException {
UncheckedCloseable close=null;
try {
Connection connection = dataSource.getConnection();
close=UncheckedCloseable.wrap(connection);
String sql = "select * from " + table;
PreparedStatement pSt = connection.prepareStatement(sql);
close=close.nest(pSt);
connection.setAutoCommit(false);
pSt.setFetchSize(5000);
ResultSet resultSet = pSt.executeQuery();
close=close.nest(resultSet);
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Record>(
Long.MAX_VALUE,Spliterator.ORDERED) {
@Override
public boolean tryAdvance(Consumer<? super Record> action) {
try {
if(!resultSet.next()) return false;
action.accept(createRecord(resultSet));
return true;
} catch(SQLException ex) {
throw new RuntimeException(ex);
}
}
}, false).onClose(close);
} catch(SQLException sqlEx) {
if(close!=null)
try { close.close(); } catch(Exception ex) { sqlEx.addSuppressed(ex); }
throw sqlEx;
}
}
_
このメソッドは、上記のユーティリティクラスの1つのインスタンス内のすべてのリソース、Connection
、Statement
、およびResultSet
に必要な終了操作をラップします。初期化中に例外が発生すると、クローズ操作がすぐに実行され、例外が呼び出し元に配信されます。ストリームの構築が成功すると、クローズ操作はonClose
を介して登録されます。
したがって、呼び出し元は次のような適切な終了を保証する必要があります
_try(Stream<Record> s=tableAsStream(dataSource, table)) {
// stream operation
}
_
SQLException
を介したRuntimeException
の配信もtryAdvance
メソッドに追加されていることに注意してください。したがって、問題なく_throws SQLException
_をcreateRecord
メソッドに追加できます。
jOOQ あなたの質問の一部に答えます。 jOOQ 3.8の時点で、jOOQとStreamの組み合わせに関連する追加機能がかなりあります。 このjOOQページには他の使用法も記載されています 。
あなたはこれを試しました:
_Stream<Record> stream = DSL.using(connection).fetch(resultSet).stream();
_
fetch(ResultSet)
は結果セット全体をメモリにフェッチしてから Collection.stream()
を呼び出すため、実際、これは大きな結果セットではうまく機能しません。 =その上。
代わりに、これを書くことができます:
_try (Stream<Record> stream = DSL.using(connection).fetchStream(resultSet)) {
...
}
_
...これは本質的に便利です:
_try (Cursor<Record> cursor = DSL.using(connection).fetchLazy(resultSet)) {
Stream<Record> stream = cursor.stream();
...
}
_
DSLContext.fetchStream(ResultSet)
も参照してください
もちろん、JDBCと格闘するのではなく、jOOQにSQL文字列を実行させることもできます。
_try (Stream<Record> stream =
DSL.using(dataSource)
.resultQuery("select * from {0}", DSL.name(table)) // Prevent SQL injection
.fetchSize(5000)
.fetchStream()) {
...
}
_
JOOQによって生成されたStream
は「リソースフル」であることに注意してください。つまり、開いているResultSet
(およびPreparedStatement
)への参照が含まれています。したがって、メソッドの外部でそのストリームを本当に返したい場合は、適切に閉じられていることを確認してください!
私はあなたのためにそれを行う有名なライブラリを知りません。
つまり、 この記事 は、結果セットをイテレータ(ResultSetIterator)でラップし、作成するために最初のパラメータとして Spliterators.spliteratorUnknownSize()
に渡す方法を示しています Spliterator
。
その後、Spliteratorを StreamSupport
で使用して、その上にStreamを作成できます。
ResultSetIterator
クラスの推奨される実装:
public class ResultSetIterator implements Iterator {
private ResultSet rs;
private PreparedStatement ps;
private Connection connection;
private String sql;
public ResultSetIterator(Connection connection, String sql) {
assert connection != null;
assert sql != null;
this.connection = connection;
this.sql = sql;
}
public void init() {
try {
ps = connection.prepareStatement(sql);
rs = ps.executeQuery();
} catch (SQLException e) {
close();
throw new DataAccessException(e);
}
}
@Override
public boolean hasNext() {
if (ps == null) {
init();
}
try {
boolean hasMore = rs.next();
if (!hasMore) {
close();
}
return hasMore;
} catch (SQLException e) {
close();
throw new DataAccessException(e);
}
}
private void close() {
try {
rs.close();
try {
ps.close();
} catch (SQLException e) {
//nothing we can do here
}
} catch (SQLException e) {
//nothing we can do here
}
}
@Override
public Tuple next() {
try {
return SQL.rowAsTuple(sql, rs);
} catch (DataAccessException e) {
close();
throw e;
}
}
}
その後:
public static Stream stream(final Connection connection,
final String sql,
final Object... parms) {
return StreamSupport
.stream(Spliterators.spliteratorUnknownSize(
new ResultSetIterator(connection, sql), 0), false);
}
AbacusUtil による最も単純なサンプルを次に示します。
final DataSource ds = JdbcUtil.createDataSource(url, user, password);
final SQLExecutor sqlExecutor = new SQLExecutor(ds);
sqlExecutor.stream(sql, parameters);
開示:私はAbacusUtilの開発者です。