ReactiveMongo:插入新文档时如何处理数据库错误

时间:2014-01-12 14:48:45

标签: mongodb scala reactivemongo

我有一个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. 如何在LastError部分处理各种错误类型(即recover提供的错误类型?)
  2. 如何确保来电者获得预期的例外(例如IllegalArgumentException)?

4 个答案:

答案 0 :(得分: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确保id中已有user,然后生成全新的Mongo ObjectID。然后,将新对象插入数据库中,最后将结果转换回外部表示(即_id => id)。如果失败,我只是使用当前错误消息创建自定义异常。我希望有所帮助。

答案 1 :(得分:1)

我的经验更多的是使用纯Java驱动程序,因此我只能评论您使用mongo的策略 -

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

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

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

另外,如果省略它,请确保为您生成id,并且如果您仍然希望对其进行编码,则可以使用更简单的查询进行唯一性检查。至少在java驱动程序中你可以做到

collection.findOne(new BasicDBObject("email",someemailAddress))

答案 2 :(得分:0)

查看更新方法的upsert模式(“如果没有匹配存在则插入新文档(Upsert)”部分):http://docs.mongodb.org/manual/reference/method/db.collection.update/#insert-a-new-document-if-no-match-exists-upsert

答案 3 :(得分:0)

我在反应动物的谷歌小组回复了一段类似的问题。您可以在恢复块中包含另一个案例以匹配LastError对象,查询其错误代码并适当地处理错误。这是最初的问题:

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