有没有一种简单的方法可以将Stream作为RowParser的输出?

时间:2012-11-07 13:32:01

标签: scala playframework-2.0 anorm

鉴于类型为rowParser的{​​{1}},根据我目前看到的代码示例,您将如何解析来自表RowParser[Photo]的行列表:

photo

def getPhotos(album: Album): List[Photo] = DB.withConnection { implicit c => SQL("select * from photo where album = {album}").on( 'album -> album.id ).as(rowParser *) } 运算符创建类型为*的解析器。现在,我想知道是否同样可以获得一个产生ResultSetParser[List[Photo]]的解析器(认为更懒惰总是更好),但我只想出了这个:

Stream

它有效,但似乎过于复杂。我当然可以在我从第一个函数获得的def getPhotos(album: Album): Stream[Photo] = DB.withConnection { implicit c => SQL("select * from photo where album = {album}").on( 'album -> album.id )() collect (rowParser(_) match { case Success(photo) => photo }) } 上调用toStream,但我的目标是仅对实际读取的行应用List。有没有更简单的方法来实现这一目标?

编辑:我知道如果事先知道感兴趣的行数,则应在查询中使用rowParser。我也知道,在很多情况下,你无论如何都会使用整个结果,所以懒惰不会提高性能。但是可能存在一些情况,您可以节省几个周期,例如如果由于某种原因,您有搜索条件,您不能或不想在SQL中表达。所以我认为奇怪的是,鉴于anorm提供了获取limit Stream的方法,我没有找到一种直接的方法来应用SqlRowRowParser

3 个答案:

答案 0 :(得分:2)

我最终创建了自己的stream方法,该方法与list方法相对应:

def stream[A](p: RowParser[A]) = new ResultSetParser[Stream[A]]  {
      def apply(rows: SqlParser.ResultSet): SqlResult[Stream[A]] = rows.headOption.map(p(_)) match {
        case None => Success(Stream.empty[A])
        case Some(Success(a)) => {
          val s: Stream[A] = a #:: rows.tail.flatMap(r => p(r) match {
            case Success(r) => Some(r)
            case _ => None
          })  

          Success(s)
        }
        case Some(Error(msg)) => Error(msg)
      }
   } 

请注意,Play SqlResult只能是Success / Error,而每行也可以是Success / Error。我只处理第一行,假设其余部分相同。这可能适用于您,也可能不适合您。

答案 1 :(得分:1)

您最好使用limitoffset制作较小的(分页)查询。

如果要将(大)结果保留在内存中并从那里流式传输,Anorm需要进行一些修改。然后另一个问题是您的JVM的新内存要求。您将如何处理服务级别的缓存?请注意,以前您可以轻松缓存photos?page=1&size=10之类的内容,但现在您只有photos,而缓存技术则不知道如何处理该流。

更糟糕的是,并且可能在JDBC级别上,将流包裹在limit ed和offset - ed execute语句中,只是在幕后对数据库进行多次调用,但是这听起来需要花费大量工作来将Scala生成的Stream代码移植到Java land(与Groovy,jRuby等一起工作),然后在批准的JDBC 5或6路线图上获取它。这个想法可能会因为过于复杂而被避开。

你可以将Stream包裹在你的整个DAO周围(limitoffset诡计会发生),但这听起来比它的价值更麻烦: - )

答案 2 :(得分:1)

当遇到转换为Streams的内置anorm函数试图解析结果集时,我遇到了类似的情况,但遇到了调用堆栈溢出异常。

为了解决这个问题,我选择放弃anorm ResultSetParser范例,然后回到java.sql.ResultSet对象。

我想使用anorm的内部类来解析结果集行,但是,从版本2.4开始,他们已经将所有相关的类和方法私有化到它们的包中,并且已经弃用了其他几个方法。本来可以更直接地使用。

我使用了Promises和Futures的组合来解决anorm现在返回的ManagedResource。我避免了所有弃用的功能。

import anorm._
import java.sql.ResultSet
import scala.concurrent._

def SqlStream[T](sql:SqlQuery)(parse:ResultSet => T)(implicit ec:ExecutionContext):Future[Stream[T]] = {
  val conn = db.getConnection()
  val mr = sql.preparedStatement(conn, false)
  val p = Promise[Unit]()
  val p2 = Promise[ResultSet]()
  Future {
    mr.map({ stmt =>
      p2.success(stmt.executeQuery)
      Await.ready(p.future, duration.Duration.Inf)
    }).acquireAndGet(identity).andThen { case _ => conn.close() }
  }
  def _stream(rs:ResultSet):Stream[T] = {
    if (rs.next()) parse(rs) #:: _stream(rs)
    else {
      p.success(())
      Stream.empty
    }
  }
  p2.future.map { rs =>
    rs.beforeFirst()
    _stream(rs)
  }
}

这个函数的一个相当简单的用法是这样的:

def getText(implicit ec:ExecutionContext):Future[Stream[String]] = {
  SqlStream(SQL("select FIELD from TABLE")) { rs => rs.getString("FIELD") }
}

这种方法当然有缺点,但是,这解决了我的问题,并且不需要包含任何其他库。