2012-03-09 7 views
33

Gdy wyszukuję bazę danych i otrzymuję wynikowy zestaw wyników (tylko do odczytu), funkcja ResultSet działa jak lista wierszy bazy danych.Traktowanie zestawu wynikowego SQL, takiego jak strumień Scala

Próbuję znaleźć sposób, aby traktować ten zestaw wyników jak Scala Stream. Umożliwi to takie operacje, jak filter, map itp., Nie zużywając przy tym dużych ilości pamięci RAM.

I wdrożone metody ogon rekurencyjnej, aby wyodrębnić poszczególne elementy, ale wymaga to, że wszystkie elementy są w pamięci w tym samym czasie, to problem, jeśli wynikowa jest bardzo duża:

// 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) 
    } 
} 
+0

Czy możesz użyć Iterable zamiast Stream, aby zrobić to, co chcesz? –

+3

Również strumień zachowa wartości w pamięci, tak więc nie będziesz faktycznie zapisywać pamięci, gdy dojdziesz do końca listy. –

+0

Myślę, że bez flagi/opcji jdbc, która powoduje, że jdbc sam przesyła wyniki, wciąż masz jedną pełną kopię danych w pamięci, zbudowaną przez twój interfejs API jdbc. – matanster

Odpowiedz

61

Nie miałem” t przetestuj to, ale dlaczego by nie zadziałało?

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

To wygląda idealnie. Przetestuję go, gdy tylko skonfiguruję bazę danych. Nie sądzę, że muszę go przekonwertować na "Stream". Mogę zastosować do niego 'map',' filter' itp. – Ralph

+1

Wypróbowałem to i zadziałało jak czar! Dzięki. – Ralph

+1

Chciałbym dać ci drugie głosowanie w górę.Dodałem ten fragment kodu do mojej biblioteki Scala Snippets. Szybko staje się jednym z moich ulubionych. – Ralph

3

Potrzebowałem czegoś podobnego. Opierając się na bardzo chłodnym odpowiedź elbowich, w I owinął go trochę, a zamiast napisu, wrócę wynik (dzięki czemu można uzyskać dowolny kolumna)

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

Musiałem przejść metadane tabeli, ale to będzie działać na wiersze tabeli (można zrobić stmt.executeQuery (sQL) zamiast md.getColumns): funkcja

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) 
     }) 
+1

Jeśli nie potrzebujesz wracać do strumienia (np. Tylko do kolejnej iteracji), możesz po prostu użyć iteratora. To znacznie redukuje obciążenie pamięci związane z używaniem strumienia (zwraca 'Iterator [ResultSet]' i upuszcza 'toStream') – Greg

8

użytkowy na odpowiedź @ elbowich za:

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

Umożliwia użycie typu wnioskowania. Np .:

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

Ponieważ wynikowa jest tylko zmienny obiekt jest poruszać się dalej, musimy zdefiniować własną koncepcję następnego rzędu. Możemy to zrobić za pomocą funkcji wejściowej następująco:

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: Przekłada się strumień lub coś innego jak powyżej.

5

To brzmi jak wielka szansa dla niejawnej klasy. Najpierw zdefiniować niejawny klasę gdzieś:

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

Następnie wystarczy zaimportować niejawny klasę gdziekolwiek są wykonywane zapytanie i określony obiekt ResultSet:

import com.company.Implicits._ 

Wreszcie uzyskać dane z wykorzystaniem metody toStream. Na przykład, uzyskaj wszystkie identyfikatory, jak pokazano poniżej:

val allIds = resultSet.toStream.map(result => result.getInt("id")) 
Powiązane problemy