ResultSet の Stream 化
java.sql.ResultSet
を java.util.stream.Stream
化する方法はいくつか考えられますが、今回は以下の方法を試してみました。
- Spliterator インターフェースの実装クラスを作成
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20151026/
Spliterator インターフェースを直接実装
java.util.Spliterator
インターフェースを直接実装する場合、以下の 4つのメソッドを実装します。
メソッド名 | 内容 | 備考 |
---|---|---|
tryAdvance | 要素を個々にトラバースする | 要素が存在する場合に引数の Consumer をその要素で実行し true を返す |
estimateSize | 残りの要素数の推定値を返す | 不明な場合は Long.MAX_VALUE を返す |
characteristics | 構造や要素の特性を返す | 複数の特性値を | で連結できる |
trySplit | 要素の一部を分割できる場合に分割する | 分割できない場合は null を返す |
今回は tryAdvance
メソッドの引数 Consumer へ ResultSet を加工した値を渡すように、SQLException を伴う処理を TryFunction インターフェースとして扱っています。
characteristics
では要素の順序が定義されている事を示す ORDERED
のみを返すようにしていますが、(今回のように加工しないで) ResultSet をそのまま使うのなら NONNULL
も追加できると思います。
なお、Stream を返す stream
メソッドは、あると便利なので定義しているだけです。 (無くても問題ありません)
src/main/java/sample/ResultSetSpliterator.java
package sample; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Spliterator; import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; public class ResultSetSpliterator<T> implements Spliterator<T> { private ResultSet resultSet; // ResultSet を加工する処理 private TryFunction<ResultSet, T, SQLException> converter; public ResultSetSpliterator(ResultSet resultSet, TryFunction<ResultSet, T, SQLException> converter) { this.resultSet = resultSet; this.converter = converter; } @Override public boolean tryAdvance(Consumer<? super T> consumer) { try { if (resultSet.next()) { consumer.accept(converter.apply(resultSet)); return true; } } catch (SQLException e) { throw new RuntimeException(e); } return false; } @Override public Spliterator<T> trySplit() { return null; } @Override public long estimateSize() { return Long.MAX_VALUE; } @Override public int characteristics() { return ORDERED; } // Stream 化して返す public Stream<T> stream() { return StreamSupport.stream(this, false); } }
TryFunction の内容は次の通りです。
src/main/java/sample/TryFunction.java
package sample; @FunctionalInterface public interface TryFunction<T, R, E extends Exception> { R apply(T t) throws E; static <T, E extends Exception> TryFunction<T, T, E> identity() { return r -> r; } }
動作確認
動作確認のための実行クラスを用意します。
src/main/java/sample/SampleApp.java
package sample; import java.sql.*; public class SampleApp { private static final String SQL = "select * from product"; public static void main(String... args) throws Exception { try (Connection con = DriverManager.getConnection(args[0])) { sample1(con); System.out.println("---"); sample2(con); } } private static void sample1(Connection con) throws SQLException { try ( PreparedStatement ps = con.prepareStatement(SQL); ResultSet rs = ps.executeQuery() ) { new ResultSetSpliterator<>( rs, r -> r.getString("name") ).stream().forEach(System.out::println); } } private static void sample2(Connection con) throws SQLException { try ( PreparedStatement ps = con.prepareStatement(SQL); ResultSet rs = ps.executeQuery() ) { long count = new ResultSetSpliterator<>( rs, TryFunction.identity() ).stream().count(); System.out.println("count : " + count); } } }
Gradle による実行結果は以下の通り。 一応、Stream として処理できているようです。
実行結果
> gradle run -Pargs="jdbc:mysql://localhost/sample?user=root" ・・・ sample1 sample2 sample3 --- count : 3
Gradle のビルド定義には以下を使いました。
build.gradle
apply plugin: 'application' mainClassName = 'sample.SampleApp' repositories { jcenter() } dependencies { runtime 'mysql:mysql-connector-java:5.1.36' } run { if (project.hasProperty('args')) { args project.args } }
Spliterators.AbstractSpliterator をサブクラス化
直接 Spliterator インターフェースを実装するよりも、Spliterators.AbstractSpliterator
のサブクラスとした方が、少しだけコードを減らせます。
この場合、estimateSize・characteristics の値は super コンストラクタで指定します。
src/main/java/sample/ResultSetSpliterator2.java
package sample; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Spliterators; import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; public class ResultSetSpliterator2<T> extends Spliterators.AbstractSpliterator<T> { private ResultSet resultSet; private TryFunction<ResultSet, T, SQLException> converter; public ResultSetSpliterator2(ResultSet resultSet, TryFunction<ResultSet, T, SQLException> converter) { // estimateSize・characteristics の値を指定 super(Long.MAX_VALUE, ORDERED); this.resultSet = resultSet; this.converter = converter; } @Override public boolean tryAdvance(Consumer<? super T> consumer) { try { if (resultSet.next()) { consumer.accept(converter.apply(resultSet)); return true; } } catch (SQLException e) { throw new RuntimeException(e); } return false; } public Stream<T> stream() { return StreamSupport.stream(this, false); } }