Подтвердить что ты не робот

Обработка SQL ResultSet, как поток Scala

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

Я пытаюсь найти способ обработать этот ResultSet, как Scala Stream. Это позволит выполнять такие операции, как filter, map и т.д., Не потребляя большие объемы ОЗУ.

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

Ответ 1

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

new Iterator[String] {
  def hasNext = resultSet.next()
  def next() = resultSet.getString(1)
}.toStream

Ответ 2

Вспомогательная функция для ответа @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))

Ответ 3

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

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"))

Ответ 4

Мне нужно что-то подобное. Основываясь на 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)
      })

Ответ 5

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

Ответ 6

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

Ответ 7

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

  new Iterator[ResultSet] {
    def hasNext = {
      !resultSet.isLast
    }
    def next() = {
      resultSet.next()
      resultSet
    }
  }