来自Scala中使用Play框架的迭代器的响应
我有一个数据库调用的大型结果集,我需要流回用户,因为它不能全部适合内存.
我可以通过设置选项来回传数据库中的结果 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框架中的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是什么或者做什么的文档.我以为一旦Enumerator消耗了TraversableOnce对象,就会从那里拿走它,但我想不是吗? 解决方法
你的方法有问题
您的方法有两个问题: >您正在将Iterator写入Enumerator / Iteratee.您应该编写迭代器的内容而不是整个迭代器 例 build.sbt 一个简单的Play Scala项目,支持H2和SQL. 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 Global.scala 配置文件,在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] } 控制器/ 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标头(采用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" } 代码组织 我在控制器中放了一些SQL代码.在这种情况下,这是完全正常的.如果代码变大,那么模型中的SQL东西可能会更好,让控制器使用更通用的(在这种情况下:“monadic plus”,即map,filter,flatMap)接口. 在控制器中,JSON代码和SQL代码混合在一起.当代码变大时,你应该组织它,例如每个技术或每个模型对象/业务领域. 阻塞迭代器 迭代器的使用会导致阻塞行为.这通常是一个大问题,但应该避免应用程序必须加载很多(每秒数百或数千次点击)或必须非常快速地回答(想想交易算法在堆栈交换机上实时工作).在这种情况下,您可以使用NoSQL数据库作为缓存(请不要将其用作唯一的数据存储)或非阻塞的JDBC(例如async postgres / mysql).同样:对于大型应用程序来说,这不是必需的 注意:只要转换为迭代器,请记住只能使用一次迭代器.对于每个请求,您需要一个新的迭代器. 结论 一个完整的WebApp,包括数据库访问完全在(不那么短)SO答案.我非常喜欢Play框架. 此代码用于教育目的.在某些地方,这是非常尴尬的,以便更容易理解初学者的概念.在一个真实的应用程序中,你会理顺这些东西,因为你已经知道了这些概念,你只是想看看代码的目的(为什么它在那里?它使用哪些工具?它什么时候做什么?)乍一看. 玩得开心! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- angularjs – 设置reloadOnSearch = false时,如何在angular
- angular – 在组件中使用NgbModule.forRoot()导致测试失败
- bash – LC_ALL = C加速grep的含义
- 如何在shell脚本中捕获SQLPlus退出代码?
- VIM命令解释总结;冗余磁盘阵列的挂载使用及冗余备份磁盘的
- Angular2,组件的多个视图?
- 【数据结构】5.1 顺序表的查找以及二分查找的实现
- 应用WSDK - 实践WebServices的路由和路由选择(下)
- 在Angular2代码中的TypeScript错误:找不到名称’module’
- erlang 接入远程shell控制台(请重点学习作业JCL模式)