web-dev-qa-db-ja.com

ResultSetを使用したjava.util.stream

大量のデータ(約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();は時間がかかりすぎる

37
Iurii

最初に理解する必要があるのは、次のようなコードです

_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つのインスタンス内のすべてのリソース、ConnectionStatement、およびResultSetに必要な終了操作をラップします。初期化中に例外が発生すると、クローズ操作がすぐに実行され、例外が呼び出し元に配信されます。ストリームの構築が成功すると、クローズ操作はonCloseを介して登録されます。

したがって、呼び出し元は次のような適切な終了を保証する必要があります

_try(Stream<Record> s=tableAsStream(dataSource, table)) {
    // stream operation
}
_

SQLExceptionを介したRuntimeExceptionの配信もtryAdvanceメソッドに追加されていることに注意してください。したがって、問題なく_throws SQLException_をcreateRecordメソッドに追加できます。

67
Holger

jOOQ

jOOQ あなたの質問の一部に答えます。 jOOQ 3.8の時点で、jOOQとStreamの組み合わせに関連する追加機能がかなりあります。 このjOOQページには他の使用法も記載されています

推奨される使用法:

あなたはこれを試しました:

_Stream<Record> stream = DSL.using(connection).fetch(resultSet).stream();
_

fetch(ResultSet) は結果セット全体をメモリにフェッチしてから Collection.stream() を呼び出すため、実際、これは大きな結果セットではうまく機能しません。 =その上。

より良い(怠yな)使用法:

代わりに、これを書くことができます:

_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()) {
    ...
}
_

Try-with-resourcesの使用について

JOOQによって生成されたStreamは「リソースフル」であることに注意してください。つまり、開いているResultSet(およびPreparedStatement)への参照が含まれています。したがって、メソッドの外部でそのストリームを本当に返したい場合は、適切に閉じられていることを確認してください!

10
Lukas Eder

私はあなたのためにそれを行う有名なライブラリを知りません。

つまり、 この記事 は、結果セットをイテレータ(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);
}
4
alfasin

AbacusUtil による最も単純なサンプルを次に示します。

final DataSource ds = JdbcUtil.createDataSource(url, user, password);
final SQLExecutor sqlExecutor = new SQLExecutor(ds);
sqlExecutor.stream(sql, parameters);

開示:私はAbacusUtilの開発者です。

3
user_3380739