Lorsque j'interroge une base de données et que je reçois un ResultSet (avant uniquement, en lecture seule), cet ensemble agit comme une liste de lignes de base de données.
J'essaie de trouver un moyen de traiter ce ResultSet comme un Scala Stream
. Cela permettra des opérations telles que filter
, map
, etc., tout en ne consommant pas de grandes quantités de RAM.
J'ai implémenté une méthode d'extrémité récursive pour extraire les éléments individuels, mais cela nécessite que tous les éléments soient en mémoire en même temps, un problème si le ResultSet est très volumineux:
// 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)
}
}
Je ne l'ai pas testé, mais pourquoi ça ne marcherait pas?
new Iterator[String] {
def hasNext = resultSet.next()
def next() = resultSet.getString(1)
}.toStream
Fonction utilitaire pour la réponse de @ elbowich:
def results[T](resultSet: ResultSet)(f: ResultSet => T) = {
new Iterator[T] {
def hasNext = resultSet.next()
def next() = f(resultSet)
}
}
Vous permet d'utiliser l'inférence de type. Par exemple.:
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))
Cela semble être une excellente opportunité pour une classe implicite. Commencez par définir la classe implicite quelque part:
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
}
}
}
Ensuite, importez simplement cette classe implicite partout où vous avez exécuté votre requête et défini l'objet ResultSet:
import com.company.Implicits._
Enfin, extrayez les données à l'aide de la méthode toStream. Par exemple, obtenez tous les identifiants comme indiqué ci-dessous:
val allIds = resultSet.toStream.map(result => result.getInt("id"))
j'avais besoin de quelque chose de similaire. En me basant sur la réponse très cool de elbowich, je l’ai emballée un peu et au lieu de la chaîne, je retourne le résultat (pour que vous puissiez obtenir n’importe quelle colonne)
def resultSetItr(resultSet: ResultSet): Stream[ResultSet] = {
new Iterator[ResultSet] {
def hasNext = resultSet.next()
def next() = resultSet
}.toStream
}
J'avais besoin d'accéder aux métadonnées de la table, mais cela fonctionnera pour les lignes de la table (peut faire un stmt.executeQuery (sql) au lieu de md.getColumns):
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)
})
Parce que ResultSet est simplement un objet mutable sur lequel nous allons naviguer, nous devons définir notre propre concept de ligne suivante. Nous pouvons le faire avec une fonction d'entrée comme suit:
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.")
}
EDIT: Traduire pour diffuser ou autre chose comme ci-dessus.
Cette implémentation, bien que plus longue et plus lourde, correspond mieux au contrat ResultSet. L'effet secondaire a été supprimé de hasNext (...) et déplacé dans 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
}
}
Je pense que la plupart des implémentations ci-dessus ont une méthode non déterministe hasNext
. L'appeler deux fois déplacera le curseur à la deuxième ligne. Je conseillerais d'utiliser quelque chose comme ça:
new Iterator[ResultSet] {
def hasNext = {
!resultSet.isLast
}
def next() = {
resultSet.next()
resultSet
}
}