2

これまで見てきたコード サンプルによるとrowParser、 typeが与えられた場合RowParser[Photo]、 table からの行のリストを解析する方法は次のとおりです。photo

def getPhotos(album: Album): List[Photo] = DB.withConnection { implicit c =>
  SQL("select * from photo where album = {album}").on(
    'album -> album.id
  ).as(rowParser *)
}

*オペレーターが type のパーサーを作成する場所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これを達成する簡単な方法はありますか?

編集:limit対象の行数が事前にわかっている場合、クエリで使用する必要があることはわかっています。また、多くの場合、とにかく結果全体を使用することになるため、怠けてもパフォーマンスは向上しないことも認識しています。ただし、いくつかのサイクルを節約できる場合もあります。たとえば、何らかの理由で、SQL で表現できない、または表現したくない検索基準がある場合などです。Streamしたがって、 anorm がofを取得する方法を提供するという事実を考えると、それに aSqlRowを適用する簡単な方法を見つけられなかったのは奇妙だと思いRowParserました。

4

3 に答える 3

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

PlaySqlResultはSuccess/Errorのいずれかのみであり、各行もSuccess/Errorである可能性があることに注意してください。残りが同じであると仮定して、これを最初の行に対してのみ処理します。これはあなたのために働くかもしれないし、そうでないかもしれません。

于 2012-12-18T12:18:00.693 に答える
1

limitandを使用して、より小さな (ページ化された) クエリを作成することをお勧めしますoffset

(大きな)結果をメモリに保持し、そこからストリーミングする場合は、Anorm に何らかの変更が必要になります。次に、他の懸念事項は、JVM の新しいメモリ要件です。また、サービス レベルでキャッシングをどのように処理しますか? 以前は のようなものを簡単にキャッシュできましphotos?page=1&size=10たが、今は だけphotosになり、キャッシュ テクノロジはストリームをどう処理するかわかりません。

さらに悪いことに、おそらく JDBC レベルでは、Stream をlimited ステートメントとoffset-edexecuteステートメントでラップし、舞台裏でデータベースを複数回呼び出すだけですが、Scala が実装する Stream コードを移植するには、かなりの作業が必要になるように思えます。 (Groovy、jRuby などで動作するように) Java ランドに生成し、JDBC 5 または 6 ロードマップの承認を得ます。このアイデアは、おそらく複雑すぎるとして敬遠されるでしょう。

StreamDAO全体をラップすることもできますが( limitandのoffsetトリッキーが発生する場所)、これは価値があるよりも多くの問題のように思えます:-)

于 2012-11-09T08:34:22.670 に答える
1

同様の状況に遭遇しましたが、ストリームに変換する組み込みの 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") }
}

もちろん、このアプローチには欠点がありますが、これは私の問題を回避し、他のライブラリを含める必要はありませんでした。

于 2015-07-27T01:29:06.027 に答える