像Scala流一样处理SQL ResultSet
当我查询数据库并接收到(只进只读)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)
}
}
我没有”吨测试它,但为什么它不工作?
new Iterator[String] {
def hasNext = resultSet.next()
def next() = resultSet.getString(1)
}.toStream
我需要类似的东西。在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)
})
如果您不需要返回流(例如,仅前向迭代),则可以使用迭代器。这大大减少了使用流的内存开销(返回'Iterator [ResultSet]',并放弃'toStream') – Greg 2014-09-15 17:32:00
效用函数:
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))
因为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.")
}
编辑: 翻译到流还是其他什么东西如同上面。
这听起来像是一个隐式类的好机会。首先某处定义的隐含类:
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"))
可以使用Iterable而不是Stream来执行您想要的操作吗? – 2012-03-09 17:19:36
另外一个流将保留内存中的值,所以当你到达列表的末尾时,你不会真正保存内存。 – 2013-07-30 13:16:53
我认为如果没有jdbc标志/选项使得jdbc自己对结果进行流式处理,那么您的内存中仍然有一个完整的数据副本,由您的jdbc api构建。 – matanster 2016-03-08 20:57:41