web-dev-qa-db-fra.com

Traiter un ResultSet SQL comme un flux Scala

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)
  }
}
38
Ralph

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
67
elbowich

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))
10
hraban

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"))
8
Jeroen Minnaert

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)
      })
3
Greg

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.

2
Brendan

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
  }
}
0
thoredge

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
    }
  }
0
Matzz