带有 Play Scala 的 ReactiveMongo

ReactiveMongo w/ Play Scala

刚接触 scala 和 reactivemongo,文档对新手不是很友好。

我在 See Bulk Insert

看到了批量插入部分

但我不知道为什么他们不显示它包含在方法中? 我期待一个包含多个对象的 JSON 数据请求。我如何设置批量插入来处理多个插入,错误可以返回。

以单插入方式为例如下:

def createFromJson = Action(parse.json) {

request =>
  try {
    val person = request.body.validate[Person].get

    val mongoResult = Await.result(collection.insert(person),Duration.apply(20,"seconds"))
    if(mongoResult.hasErrors) throw new Exception(mongoResult.errmsg.getOrElse("something unknown"))


    Created(Json.toJson(person))
}
catch {
    case e: Exception => BadRequest(e.getMessage)
}

}

这是一个完整的示例,您可以如何操作:

class ExampleController @Inject()(database: DefaultDB) extends Controller {

  case class Person(firstName: String, lastName: String)

  val personCollection: BSONCollection = database.collection("persons")
  implicit val PersonJsonReader: Reads[Person] = Json.reads[Person]
  implicit val PersonSeqJsonReader: Reads[Seq[Person]] = Reads.seq(PersonJsonReader)
  implicit val PersonJsonWriter: Writes[Person] = Json.writes[Person]
  implicit val PersonSeqJsonWriter: Writes[Seq[Person]] = Writes.seq(PersonJsonWriter)
  implicit val PersonBsonWriter = Macros.writer[Person]

  def insertMultiple = Action.async(parse.json) { implicit request =>
    val validationResult: JsResult[Seq[Person]] = request.body.validate[Seq[Person]]

    validationResult.fold(
      invalidValidationResult => Future.successful(BadRequest),
      // [1]
      validValidationResult => {
        val bulkDocs = validValidationResult.
          map(implicitly[personCollection.ImplicitlyDocumentProducer](_))

        personCollection.bulkInsert(ordered = true)(bulkDocs: _*).map {
          case insertResult if insertResult.ok =>
            Created(Json.toJson(validationResult.get))
          case insertResult => 
            InternalServerError
        }
      }
    )
  }
}

所有内容都在 [1] 之后的行中。 validValidationResult 是一个 Seq[Person] 类型的变量,此时包含有效数据。这就是我们要插入数据库的内容。

为此,我们需要通过将每个文档映射到目标集合的 ImplicitlyDocumentProducer(此处为 personCollection)来准备文档。那就是 Seq[personCollection.ImplicitlyDocumentProducer] 类型的 bulkDocs。你可以只使用 bulkInsert():

personCollection.bulkInsert(ordered = true)(bulkDocs: _*)

我们在这里使用 _* 来拼接 Seq,因为 bulkInsert() 需要可变参数而不是 Seq。参见 this thread for more info about it。基本上已经这样了。

剩余代码正在处理播放结果并验证收到的请求正文以确保它包含有效数据。

这里有一些使用 play/reactivemongo/scala/futures 的一般技巧:

避免Await.result。您基本上不需要在生产代码中使用它。 futures 背后的想法是执行 非阻塞 操作。用 Await.result 让他们再次阻塞达不到目的。它对于调试或测试代码很有用,但即便如此,通常也有更好的方法来解决问题。 Scala futures(与 java 不同)非常强大,您可以用它们做很多事情,例如参见flatMap/map/filter/foreach/.. 在未来的 scaladoc 中。例如,上面的代码正是使用了它。它在控制器方法中使用 Action.async 而不是 Action。这意味着它必须 return 一个 Future[Result] 而不是 Result。这很棒,因为 ReactiveMongo return 为所有操作提供了一堆 Futures。所以你所要做的就是执行 bulkInsert,其中 return 是一个 Future 并使用 map() 将 returned Future[MultiBulkWriteResult] 映射到 Future[Result] .这导致没有阻塞,并且 play 可以与 returned future 一起工作。

当然上面的例子可以改进一点,我尽量保持简单。 例如,当 returning BadRequest(请求主体验证失败)或 InternalServerError(数据库写入失败)时,您应该 return 正确的错误消息。您可以从 invalidValidationResultinsertResult 获取有关错误的更多信息。您可以使用 Formats 而不是那么多 Reads/Writes (也可以将它们用于 ReactiveMongo)。查看播放 json 文档以及反应式 mongo 文档以获取更多信息。

虽然之前的回答是正确的。 我们可以使用 JSONCollection

减少样板
package controllers

import javax.inject._

import play.api.libs.json._
import play.api.mvc._
import play.modules.reactivemongo._
import reactivemongo.play.json.collection.{JSONCollection, _}
import utils.Errors

import scala.concurrent.{ExecutionContext, Future}


case class Person(name: String, age: Int)

object Person {
  implicit val formatter = Json.format[Person]
}

@Singleton
class PersonBulkController @Inject()(val reactiveMongoApi: ReactiveMongoApi)(implicit exec: ExecutionContext) extends Controller with MongoController with ReactiveMongoComponents {

  val persons: JSONCollection = db.collection[JSONCollection]("person")

  def createBulkFromJson = Action.async(parse.json) { request =>

    Json.fromJson[Seq[Person]](request.body) match {
      case JsSuccess(newPersons, _) =>
        val documents = newPersons.map(implicitly[persons.ImplicitlyDocumentProducer](_))

        persons.bulkInsert(ordered = true)(documents: _*).map{ multiResult =>
          Created(s"Created ${multiResult.n} persons")
        }

      case JsError(errors) =>
        Future.successful(BadRequest("Could not build an array of persons from the json provided. " + errors))
    }
  }
}

在build.sbt

libraryDependencies ++= Seq(
  "org.reactivemongo" %% "play2-reactivemongo" % "0.11.12"
)

已使用 play 2.5.1 进行测试,尽管它应该可以在以前版本的 play 中编译。

仅供参考,正如前面的答案所说,有两种方法可以操作 JSON 数据:使用 ReactiveMongo 模块 + Play JSON 库,或者使用 ReactiveMongo 的 BSON 库。

Play Framework 的 ReactiveMongo 模块的文档是 available online。您可以在那里找到代码示例。