gpt4 book ai didi

scala - Scala中具有Play框架的迭代器的分块响应

转载 作者:行者123 更新时间:2023-12-04 14:26:55 25 4
gpt4 key购买 nike

我从数据库调用中得到了一个很大的结果集,我需要将其流回给用户,因为它不能全部放入内存中。

我可以通过设置选项从数据库中流回结果

val statement = session.conn.prepareStatement(query, 
java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY)
statement.setFetchSize(Integer.MIN_VALUE)
....
....
val res = statement.executeQuery

然后通过使用迭代器
val result = new Iterator[MyResultClass] {
def hasNext = res.next
def next = MyResultClass(someValue = res.getString("someColumn"), anotherValue = res.getInt("anotherValue"))
}

在Scala中,Iterator扩展了TraversableOnce,这应允许我根据 https://www.playframework.com/documentation/2.3.x/ScalaStream中的文档将Iterator传递给 Play Framework 中用于Chunked Response的Enumerator类。

在查看Enumerator的源代码时,我发现它有一个重载的apply方法来使用TraversableOnce对象

我尝试使用以下代码
import play.api.libs.iteratee.Enumerator
val dataContent = Enumerator(result)
Ok.chunked(dataContent)

但这不起作用,因为它引发以下异常
Cannot write an instance of Iterator[MyResultClass] to HTTP response. Try to define a Writeable[Iterator[MyResultClass]]

在文档中找不到任何有关Writable是或做什么的内容。我认为,一旦枚举器使用了TraversableOnce对象,它将从那里获取它,但我想不是吗?

最佳答案

您的方法存在问题

您的方法存在两个问题:

  • 您正在将Iterator写入Enumerator/Iteratee。您应该编写Iterator的内容,而不是整个Iterator的内容
  • Scala不知道如何在HTTP流上表达MyResultClass的对象。在编写它们之前,请尝试将它们转换为String表示形式(例如JSON)。

  • 例子

    build.sbt

    一个具有H2和SQL支持的简单Play Scala项目。
    lazy val root = (project in file(".")).enablePlugins(PlayScala)

    scalaVersion := "2.11.6"

    libraryDependencies ++= Seq(
    jdbc,
    "org.scalikejdbc" %% "scalikejdbc" % "2.2.4",
    "com.h2database" % "h2" % "1.4.185",
    "ch.qos.logback" % "logback-classic" % "1.1.2"
    )

    项目/plugins.sbt

    仅是当前稳定版本中sbt play插件的最低配置
    resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"

    addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.8")

    conf/路由

    /json上只有一条路线
    GET    /json                        controllers.Application.json

    全局标度

    配置文件,在Play应用程序启动期间创建演示数据并用演示数据填充数据库
    import play.api.Application
    import play.api.GlobalSettings
    import scalikejdbc._

    object Global extends GlobalSettings {

    override def onStart(app : Application): Unit = {

    // initialize JDBC driver & connection pool
    Class.forName("org.h2.Driver")
    ConnectionPool.singleton("jdbc:h2:mem:hello", "user", "pass")

    // ad-hoc session provider
    implicit val session = AutoSession


    // Create table
    sql"""
    CREATE TABLE persons (
    customer_id SERIAL NOT NULL PRIMARY KEY,
    first_name VARCHAR(64),
    sure_name VARCHAR(64)
    )""".execute.apply()

    // Fill table with demo data
    Seq(("Alice", "Anderson"), ("Bob", "Builder"), ("Chris", "Christoph")).
    foreach { case (firstName, sureName) =>
    sql"INSERT INTO persons (first_name, sure_name) VALUES (${firstName}, ${sureName})".update.apply()
    }
    }
    }

    型号/Person.scala

    在这里,我们定义数据库模式和数据库对象的Scala表示形式。这里的关键是函数 personWrites。它将Person对象转换为JSON表示形式(实际代码由宏方便地生成)。
    package models

    import scalikejdbc._
    import scalikejdbc.WrappedResultSet
    import play.api.libs.json._

    case class Person(customerId : Long, firstName: Option[String], sureName : Option[String])

    object PersonsTable extends SQLSyntaxSupport[Person] {
    override val tableName : String = "persons"
    def apply(rs : WrappedResultSet) : Person =
    Person(rs.long("customer_id"), rs.stringOpt("first_name"), rs.stringOpt("sure_name"))
    }

    package object models {
    implicit val personWrites: Writes[Person] = Json.writes[Person]
    }

    Controller /Application.scala

    在这里,您有Iteratee/Enumerator代码。首先,我们从数据库中读取数据,然后将结果转换为Iterator,然后转换为Enumerator。该Enumerator不会有用,因为它的内容是 Person对象,而Play不知道如何通过HTTP编写此类对象。但是借助 personWrites,我们可以将这些对象转换为JSON。而且Play知道如何通过HTTP编写JSON。
    package controllers

    import play.api.libs.json.JsValue
    import play.api.mvc._
    import play.api.libs.iteratee._
    import scala.concurrent.ExecutionContext.Implicits.global
    import scalikejdbc._

    import models._
    import models.personWrites

    object Application extends Controller {

    implicit val session = AutoSession

    val allPersons : Traversable[Person] = sql"SELECT * FROM persons".map(rs => PersonsTable(rs)).traversable().apply()
    def personIterator(): Iterator[Person] = allPersons.toIterator
    def personEnumerator() : Enumerator[Person] = Enumerator.enumerate(personIterator)
    def personJsonEnumerator() : Enumerator[JsValue] = personEnumerator.map(personWrites.writes(_))

    def json = Action {
    Ok.chunked(personJsonEnumerator())
    }
    }

    讨论

    数据库配置

    在此示例中,数据库配置是黑客。通常我们会配置Play,以便它提供数据源并在后台处理所有数据库内容。

    JSON转换

    在代码中,我直接调用JSON转换。有更好的方法,导致代码更紧凑(但对于初学者来说更容易理解)。

    您收到的响应不是真正有效的JSON。例子:
    {"customerId":1,"firstName":"Alice","sureName":"Anderson"}
    {"customerId":2,"firstName":"Bob","sureName":"Builder"}
    {"customerId":3,"firstName":"Chris","sureName":"Christoph"}

    (备注:换行符仅用于格式化。在网络上看起来像这样:
    ...son"}{"custom...

    相反,您将有效的JSON块分块在一起。那就是你的要求。接收端可以自己消耗每个块。但是有一个问题:您必须找到某种方法将响应分成有效的块。

    该请求本身确实是分块的。考虑以下HTTP header (以JSON HAR格式,从Google Chrome导出):
         "status": 200,
    "statusText": "OK",
    "httpVersion": "HTTP/1.1",
    "headers": [
    {
    "name": "Transfer-Encoding",
    "value": "chunked"
    },
    {
    "name": "Content-Type",
    "value": "application/json; charset=utf-8"
    }

    代码组织

    我在 Controller 中放入了一些SQL代码。在这种情况下,这是完全可以的。如果代码变大,则最好使用模型中的SQL内容,并让 Controller 使用更通用的接口(interface)(在这种情况下为“monadic plus”,即 mapfilterflatMap)接口(interface)。

    在 Controller 中,JSON代码和SQL代码混合在一起。当代码变大时,您应该组织它,例如每个技术或每个模型对象/业务领域。

    阻塞迭代器

    迭代器的使用导致阻塞行为。这通常是个大问题,但是对于应用程序,必须避免必须承受大量负载(每秒数百或数千次命中)或必须快速响应(例如交易算法在栈交换中有效)的应用,应避免这种情况。在这种情况下,您可以使用NoSQL数据库作为缓存(请不要将其用作唯一的数据存储)或非阻塞JDBC(例如 async postgres / mysql)。再次:对于大型应用程序,这不是必需的。

    注意:转换为迭代器后,请记住您只能使用一次迭代器。对于每个请求,您都需要一个新的迭代器。

    结论

    一个完整的WebApp,包括一个完全(不是那么短的)SO答案中的数据库访问。我真的很喜欢Play框架。

    此代码用于教育目的。在某些地方,它太尴尬了,以使初学者更容易理解这些概念。在实际的应用程序中,您应该弄清楚这些事情,因为您已经知道了这些概念,并且只想查看代码的目的(为什么要使用它?使用了哪些工具?何时使用了什么?)。乍一看。

    玩得开心!

    关于scala - Scala中具有Play框架的迭代器的分块响应,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28886743/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com