データベースにクエリを送信して(転送のみ、読み取り専用)ResultSetを受け取ると、ResultSetはデータベース行のリストのように機能します。
このResultSetをScala Stream
として扱う方法を見つけようとしています。これにより、filter
、map
、など、大量のRAMを消費しません。
個々の項目を抽出するために末尾再帰メソッドを実装しましたが、これにはすべての項目が同時にメモリ内にある必要があり、ResultSetが非常に大きい場合に問題が発生します。
// Iterate through the result set and gather all of the String values into a list
// then return that list
@tailrec
def loop(resultSet: ResultSet,
accumulator: List[String] = List()): List[String] = {
if (!resultSet.next) accumulator.reverse
else {
val value = resultSet.getString(1)
loop(resultSet, value +: accumulator)
}
}
私はそれをテストしませんでした、しかしそれはなぜうまくいかないのですか?
new Iterator[String] {
def hasNext = resultSet.next()
def next() = resultSet.getString(1)
}.toStream
@elbowichの回答のユーティリティ関数:
def results[T](resultSet: ResultSet)(f: ResultSet => T) = {
new Iterator[T] {
def hasNext = resultSet.next()
def next() = f(resultSet)
}
}
型推論を使用できます。例えば。:
stmt.execute("SELECT mystr, myint FROM mytable")
// Example 1:
val it = results(stmt.resultSet) {
case rs => rs.getString(1) -> 100 * rs.getInt(2)
}
val m = it.toMap // Map[String, Int]
// Example 2:
val it = results(stmt.resultSet)(_.getString(1))
これは暗黙のクラスにとって絶好の機会のように思えます。まず、暗黙のクラスをどこかに定義します。
import Java.sql.ResultSet
object Implicits {
implicit class ResultSetStream(resultSet: ResultSet) {
def toStream: Stream[ResultSet] = {
new Iterator[ResultSet] {
def hasNext = resultSet.next()
def next() = resultSet
}.toStream
}
}
}
次に、クエリを実行してResultSetオブジェクトを定義した場所に、この暗黙のクラスをインポートします。
import com.company.Implicits._
最後に、toStreamメソッドを使用してデータを取得します。たとえば、次のようにすべてのIDを取得します。
val allIds = resultSet.toStream.map(result => result.getInt("id"))
私は似たようなものが必要でした。エルボウィッチの非常に優れた答えに基づいて、私はそれを少しラップし、文字列の代わりに結果を返します(したがって、任意の列を取得できます)
def resultSetItr(resultSet: ResultSet): Stream[ResultSet] = {
new Iterator[ResultSet] {
def hasNext = resultSet.next()
def next() = resultSet
}.toStream
}
テーブルのメタデータにアクセスする必要がありましたが、これはテーブルの行に対して機能します(md.getColumnsの代わりにstmt.executeQuery(sql)を実行できます)。
val md = connection.getMetaData()
val columnItr = resultSetItr( md.getColumns(null, null, "MyTable", null))
val columns = columnItr.map(col => {
val columnType = col.getString("TYPE_NAME")
val columnName = col.getString("COLUMN_NAME")
val columnSize = col.getString("COLUMN_SIZE")
new Column(columnName, columnType, columnSize.toInt, false)
})
ResultSetはnextによってナビゲートされる単なる可変オブジェクトなので、次の行の独自の概念を定義する必要があります。次のように入力関数を使用してこれを行うことができます。
class ResultSetIterator[T](rs: ResultSet, nextRowFunc: ResultSet => T)
extends Iterator[T] {
private var nextVal: Option[T] = None
override def hasNext: Boolean = {
val ret = rs.next()
if(ret) {
nextVal = Some(nextRowFunc(rs))
} else {
nextVal = None
}
ret
}
override def next(): T = nextVal.getOrElse {
hasNext
nextVal.getOrElse( throw new ResultSetIteratorOutOfBoundsException
)}
class ResultSetIteratorOutOfBoundsException extends Exception("ResultSetIterator reached end of list and next can no longer be called. hasNext should return false.")
}
編集:上記のようにストリームまたは何か他のものに翻訳します。
Iterator
契約を尊重してhasNext
に副作用がないソリューションが必要な場合のための、Sergey Alaevおよびthoredgeのソリューションと同様の代替案を次に示します。
関数を想定するとf: ResultSet => T
:
Iterator.unfold(resultSet.next()) { hasNext =>
Option.when(hasNext)(f(resultSet), resultSet.next())
}
map
にResultSet
"拡張メソッド"を含めると便利です。
implicit class ResultSetOps(resultSet: ResultSet) {
def map[T](f: ResultSet => T): Iterator[T] = {
Iterator.unfold(resultSet.next()) { hasNext =>
Option.when(hasNext)(f(resultSet), resultSet.next())
}
}
}
この実装は、より長くて扱いにくいですが、ResultSetコントラクトとの対応が優れています。副作用はhasNext(...)から削除され、next()に移動されました。
new Iterator[String] {
private var available = resultSet.next()
override def hasNext: Boolean = available
override def next(): String = {
val string = resultSet.getString(1)
available = resultSet.next()
string
}
}
上記の実装のほとんどには、非決定的なhasNext
メソッドがあると思います。 2回呼び出すと、カーソルが2行目に移動します。私はそのようなものを使うことを勧めます:
new Iterator[ResultSet] {
def hasNext = {
!resultSet.isLast
}
def next() = {
resultSet.next()
resultSet
}
}
Iterator.continually(rs.next())
.takeWhile(identity)
.map(_ => Model(
id = rs.getInt("id"),
text = rs.getString("text")
))