2012-03-09 25 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"))