Обработка SQL ResultSet как потока Scala - PullRequest
41 голосов
/ 09 марта 2012

Когда я запрашиваю базу данных и получаю обратно (только для пересылки, только для чтения) ResultSet, ResultSet действует как список строк базы данных.

Я пытаюсь найти способ обработать этот ResultSetкак скала Stream.Это позволит выполнять такие операции, как filter, map и т. Д., При этом не потребляя большого объема оперативной памяти.

Я реализовал хвост-рекурсивный метод для извлечения отдельных элементов, но для этого требуется, чтобы все элементыбыть в памяти одновременно, проблема, если 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)
  }
}

Ответы [ 8 ]

71 голосов
/ 09 марта 2012

Я не проверял, но почему бы не сработать?

new Iterator[String] {
  def hasNext = resultSet.next()
  def next() = resultSet.getString(1)
}.toStream
10 голосов
/ 30 апреля 2015

Функция полезности для ответа @ elbowich:

def results[T](resultSet: ResultSet)(f: ResultSet => T) = {
  new Iterator[T] {
    def hasNext = resultSet.next()
    def next() = f(resultSet)
  }
}

Позволяет использовать вывод типа. E.g.:

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))
8 голосов
/ 29 сентября 2016

Это звучит как отличная возможность для неявного класса.Сначала определите где-то неявный класс:

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.Например, получите все идентификаторы, как показано ниже:

val allIds = resultSet.toStream.map(result => result.getInt("id"))
3 голосов
/ 19 августа 2014

Мне нужно что-то подобное. Основываясь на очень крутом ответе elbowich, я немного завернул его, и вместо строки я возвращаю результат (чтобы вы могли получить любой столбец)

def resultSetItr(resultSet: ResultSet): Stream[ResultSet] = {
    new Iterator[ResultSet] {
      def hasNext = resultSet.next()
      def next() = resultSet
    }.toStream
  }

Мне нужно было получить доступ к метаданным таблицы, но это будет работать для строк таблицы (можно сделать stmt.executeQuery (sql) вместо 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)
      })
2 голосов
/ 17 мая 2016

Поскольку ResultSet - это просто изменяемый объект, по которому перемещается следующий объект, нам необходимо определить собственную концепцию следующей строки. Мы можем сделать это с помощью функции ввода следующим образом:

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: Перевести на поток или что-то еще, как указано выше.

0 голосов
/ 13 марта 2019
Iterator.continually(rs.next())
  .takeWhile(identity)
  .map(_ => Model(
      id = rs.getInt("id"),
      text = rs.getString("text")
   ))
0 голосов
/ 07 декабря 2018

Я думаю, что большинство из приведенных выше реализаций имеет недетерминированный метод hasNext. Вызов его два раза переместит курсор на второй ряд. Я бы посоветовал использовать что-то подобное:

  new Iterator[ResultSet] {
    def hasNext = {
      !resultSet.isLast
    }
    def next() = {
      resultSet.next()
      resultSet
    }
  }
0 голосов
/ 23 мая 2018

Эта реализация, хотя и более длинная и неуклюжая, лучше соответствует контракту 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
  }
}
...