2

我有一个 MongoDB 集合,我在其中存储User这样的文档:

{
    "_id" : ObjectId("52d14842ed0000ed0017cceb"),
    "email": "joe@gmail.com",
    "firstName": "Joe"
    ...
}

用户必须是唯一的电子邮件地址,所以我为该email字段添加了一个索引:

collection.indexesManager.ensure(
  Index(List("email" -> IndexType.Ascending), unique = true)
)

这是我插入新文档的方式:

def insert(user: User): Future[User] = {
  val json = user.asJson.transform(generateId andThen copyKey(publicIdPath, privateIdPath) andThen publicIdPath.json.prune).get
  collection.insert(json).map { lastError =>
    User(json.transform(copyKey(privateIdPath, publicIdPath) andThen privateIdPath.json.prune).get).get
  }.recover {
    throw new IllegalArgumentException(s"an user with email ${user.email} already exists")
  }
} 

如果发生错误,上面的代码会抛出一个IllegalArgumentException并且调用者能够相应地处理它。但是如果我recover像这样修改部分......

def insert(user: User): Future[User] = {
  val json = user.asJson.transform(generateId andThen copyKey(publicIdPath, privateIdPath) andThen publicIdPath.json.prune).get
  collection.insert(json).map { lastError =>
    User(json.transform(copyKey(privateIdPath, publicIdPath) andThen privateIdPath.json.prune).get).get
  }.recover {
    case e: Throwable => throw new IllegalArgumentException(s"an user with email ${user.email} already exists")
  }
}

...我不再得到一个IllegalArgumentException,但我得到了这样的东西:

play.api.Application$$anon$1: Execution exception[[IllegalArgumentException: DatabaseException['E11000 duplicate key error index: gokillo.users.$email_1  dup key: { : "giuseppe.greco@agamura.com" }' (code = 11000)]]]

...并且调用者不再能够按应有的方式处理异常。现在真正的问题是:

  1. 如何处理该部分中的各种错误类型(即由 提供的错误类型LastErrorrecover
  2. 如何确保调用者获得预期的异常(例如IllegalArgumentException)?
4

4 回答 4

2

最后我能够正确地管理事情。下面是如何使用 ReactiveMongo 插入用户并处理可能的异常:

val idPath = __ \ 'id
val oidPath = __ \ '_id

/**
  * Generates a BSON object id.
  */
protected val generateId = __.json.update(
  (oidPath \ '$oid).json.put(JsString(BSONObjectID.generate.stringify))
)

/**
  * Converts the current JSON into an internal representation to be used
  * to interact with Mongo.
  */
protected val toInternal = (__.json.update((oidPath \ '$oid).json.copyFrom(idPath.json.pick))
  andThen idPath.json.prune
)

/**
  * Converts the current JSON into an external representation to be used
  * to interact with the rest of the world.
  */
protected val toExternal = (__.json.update(idPath.json.copyFrom((oidPath \ '$oid).json.pick))
  andThen oidPath.json.prune
)

...

def insert(user: User): Future[User] = {
  val json = user.asJson.transform(idPath.json.prune andThen generateId).get
  collection.insert(json).transform(
    success => User(json.transform(toExternal).get).get,
    failure => DaoServiceException(failure.getMessage)
  )
}

user参数是一个类似 POJO 的实例,在 JSON 中具有内部表示 -User实例始终包含有效的 JSON,因为它是在构造函数中生成和验证的,我不再需要检查是否user.asJson.transform失败。

首先transform确保其中没有iduser然后生成一个全新的 Mongo ObjectID。然后,将新对象插入数据库,最后将结果转换回外部表示(即_id=> id)。如果失败,我只是使用当前错误消息创建一个自定义异常。我希望这会有所帮助。

于 2014-01-17T21:50:53.807 回答
1

我的经验更多是使用纯 java 驱动程序,所以我只能评论您使用 mongo 的一般策略 -

在我看来,您通过事先进行查询所完成的只是重复 mongos 唯一性检查。即使这样,由于可能的失败,您仍然必须向上渗透异常。这不仅速度较慢,而且容易受到竞争条件的影响,因为查询 + 插入的组合不是原子的。在那种情况下,你会有

  • 请求1:尝试插入。电子邮件存在吗?false - 继续插入
  • 请求2:尝试插入。电子邮件存在吗?false - 继续插入
  • 请求1:成功
  • 请求2:mongo会抛出数据库异常。

如果发生这种情况,让 mongo 抛出 db 异常并抛出你自己的非法参数不是更简单吗?

此外,如果您省略它,可以肯定会为您生成 id,并且如果您仍然想要编码它,那么有一个更简单的查询来进行唯一性检查。至少在java驱动程序中你可以做到

collection.findOne(new BasicDBObject("email",someemailAddress))
于 2014-01-12T14:59:41.417 回答
0

不久前,我在 reactivemongo 的 google group 上问了一个类似的问题。您可以在恢复块中使用另一种情况来匹配 LastError 对象,查询其错误代码并适当地处理错误。这是原始问题:

https://groups.google.com/forum/#!searchin/reactivemongo/alvaro$20naranjo/reactivemongo/FYUm9x8AMVo/LKyK01e9VEMJ

于 2014-02-19T17:31:13.930 回答
0

查看更新方法的 upsert 模式(“如果不存在匹配项(Upsert)”部分,则插入新文档): http ://docs.mongodb.org/manual/reference/method/db.collection.update/#插入新文档,如果不匹配存在-upsert

于 2014-01-12T18:41:25.460 回答