2012-03-09 58 views
33

當我查詢數據庫並接收到(只進只讀)ResultSet時,ResultSet的行爲與數據庫行列表相似。像Scala流一樣處理SQL ResultSet

我想找到一些方法來對待這個ResultSet,比如Scala Stream。這將允許諸如filter,map等的操作,而不消耗大量的RAM。

我實現了一個尾遞歸的方法來提取單個項目,但是這要求所有項目在記憶的同時,一個問題,如果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) 
    } 
} 
+0

可以使用Iterable而不是Stream來執行您想要的操作嗎? – 2012-03-09 17:19:36

+3

另外一個流將保留內存中的值,所以當你到達列表的末尾時,你不會真正保存內存。 – 2013-07-30 13:16:53

+0

我認爲如果沒有jdbc標誌/選項使得jdbc自己對結果進行流式處理,那麼您的內存中仍然有一個完整的數據副本,由您的jdbc api構建。 – matanster 2016-03-08 20:57:41

回答

61

我沒有」噸測試它,但爲什麼它不工作?

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

看起來很完美。一旦我建立了數據庫,我就會測試它。我甚至不認爲我需要將它轉換爲「Stream」。我可以將'map','filter'等直接應用到它。 – Ralph 2012-03-10 13:43:34

+1

嘗試過它,它像一個魅力工作!謝謝。 – Ralph 2012-03-12 16:46:34

+1

我想給你第二次投票。我已將此代碼片段添加到我的Scala片段庫。它很快成爲我的最愛之一。 – Ralph 2012-03-22 11:56:33

3

我需要類似的東西。在elbowich的非常酷的答案的基礎上,我把它包了一下,而不是字符串,我返回結果(這樣你可以得到任何列)

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

我需要訪問表的元數據,而這會爲工作表行(可以做一個stmt.executeQuery(SQL),而不是md.getColumns):對@ elbowich的回答

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

如果您不需要返回流(例如,僅前向迭代),則可以使用迭代器。這大大減少了使用流的內存開銷(返回'Iterator [ResultSet]',並放棄'toStream') – Greg 2014-09-15 17:32:00

8

效用函數:

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

因爲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.") 
} 

編輯: 翻譯到流還是其他什麼東西如同上面。

5

這聽起來像是一個隱式類的好機會。首先某處定義的隱含類:

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方法。例如,獲取所有ID,如下所示:

val allIds = resultSet.toStream.map(result => result.getInt("id"))