将SQL结果集视为Scala流
当我查询一个数据库并收到一个(仅转发、只读)结果集时,结果集的作用就像一个数据库行列表 我正试图找到某种方法,将此结果集视为Scala将SQL结果集视为Scala流,scala,stream,resultset,Scala,Stream,Resultset,当我查询一个数据库并收到一个(仅转发、只读)结果集时,结果集的作用就像一个数据库行列表 我正试图找到某种方法,将此结果集视为Scala流。这将允许诸如过滤器、映射等操作,同时不消耗大量RAM 我实现了一个尾部递归方法来提取单个项,但这需要所有项同时在内存中,如果结果集非常大,这是一个问题: // Iterate through the result set and gather all of the String values into a list // then return that li
流
。这将允许诸如过滤器
、映射
等操作,同时不消耗大量RAM
我实现了一个尾部递归方法来提取单个项,但这需要所有项同时在内存中,如果结果集非常大,这是一个问题:
// 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非常酷的答案的基础上,我对它进行了一点包装,并返回结果,而不是字符串(这样您就可以得到任何列) 我需要访问表元数据,但这将适用于表行(可以执行stmt.executeQuery(sql)而不是md.getColumns):
@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))
因为ResultSet只是一个由next导航的可变对象,所以我们需要定义自己的下一行概念。我们可以通过如下输入函数来实现:
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"))
这种实现虽然更长、更笨拙,但与ResultSet契约的对应性更好。副作用已从hasNext(…)中移除并移动到next()
我认为上面的大多数实现都有一个不确定的
hasNext
方法。调用两次会将光标移动到第二行。我建议使用类似的方法:
new Iterator[ResultSet] {
def hasNext = {
!resultSet.isLast
}
def next() = {
resultSet.next()
resultSet
}
}
这里有一个替代方案,类似于Sergey Alaev和thoredge的解决方案,当我们需要一个尊重
迭代器
合同的解决方案时,hasNext
没有副作用
假设函数f:ResultSet=>T
:
Iterator.unfold(resultSet.next()){hasNext=>
Option.when(hasNext)(f(resultSet),resultSet.next())
}
我发现在ResultSet
上使用asmap
“extension method”很有用
隐式类resultsetop(resultSet:resultSet){
def-map[T](f:ResultSet=>T):迭代器[T]={
Iterator.unfold(resultSet.next()){hasNext=>
Option.when(hasNext)(f(resultSet),resultSet.next())
}
}
}
上述版本的另一个变体,适用于Scala 2.12:
implicit class ResultSetOps(resultSet: ResultSet) {
def map[T](f: ResultSet => T): Iterator[T] =
Iterator.continually(resultSet).takeWhile(_.next()).map(f)
}
你能用Iterable代替Stream来做你想做的事情吗?另外,Stream会将值保留在内存中,因此当你到达列表的末尾时,你不会真正节省内存。我认为,如果没有jdbc标志/选项使jdbc本身将结果流化,那么内存中仍然有一个完整的数据副本,由JDBCAPI构建。看起来很完美。我将在数据库设置好后立即测试它。我甚至不认为我需要将其转换为
流。我可以直接对它应用map
,filter
等。我想给你第二次投票。我已将此代码片段添加到Scala代码段库中。它很快成为我的最爱之一。这是一个很酷的解决方案,但我担心。我认为Iterator
的通常约定是hasNext
没有副作用。它可以在两次调用next
之间被调用任意次数。有什么东西可以阻止这成为一个问题吗?mysql connector java不适用于我。不确定我是否做错了什么,但是我的ResultSet
在第二次next()
调用时关闭了,因此我只能检索一个结果行。在获取所有行之前,它不会自动关闭的唯一方法似乎是使用while(rs.next()){…}
,因此我将项目单独添加到while
中的scala.collection.mutable.ListBuffer
。看起来不太漂亮,但想不出其他方法。@Nick使用新迭代器[String]{…}。toList
而不是。toStream
将立即获取整个结果集,而不仅仅是第一行。如果您不需要返回流(例如,仅向前迭代),您可以使用迭代器。这大大减少了使用流时的内存开销(返回迭代器[ResultSet]
,并删除到流
)是否确实有效?它在DB2上失败,结果集关闭。如果这在您的情况下起作用,可能取决于特定的数据库品牌和/或配置?确实如此,但您只能在连接保持打开的情况下使用流。如果关闭连接,流将失败,迭代器也将失败。
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
}
}
new Iterator[ResultSet] {
def hasNext = {
!resultSet.isLast
}
def next() = {
resultSet.next()
resultSet
}
}
Iterator.continually(rs.next())
.takeWhile(identity)
.map(_ => Model(
id = rs.getInt("id"),
text = rs.getString("text")
))
implicit class ResultSetOps(resultSet: ResultSet) {
def map[T](f: ResultSet => T): Iterator[T] =
Iterator.continually(resultSet).takeWhile(_.next()).map(f)
}