如何捕获重复键值违规的光滑postgres异常

时间:2014-11-22 18:23:02

标签: scala playframework slick

我的表在postgresql数据库中的一对列上有唯一索引。

我想知道在插入时如何捕获重复的键异常:

def save(user: User)(implicit session: Session): User = {
  val newId = (users returning users.map(_id) += user
  user.copy(id = newId)
}

我的日志显示此异常:

Execution exception[[PSQLException: ERROR: duplicate key value violates unique constraint "...."

我还没有真正在scala中使用异常。

2 个答案:

答案 0 :(得分:26)

您的save方法应该返回与User不同的内容,以指示失败的可能性。如果抛出的唯一异常是由唯一键,并且您真的只关心成功或失败(而不是失败的类型),那么一种方法是返回Option[User]

您可以使用简单的try/catch块,将成功保存映射到Some[User]PSQLException映射到None

def save(user: User)(implicit session: Session): Option[User] = {
  try {
    val newId = (users returning users.map(_id) += user
    Some(user.copy(id = newId))
  } catch {
      case PSQLException => None
  }
}

个人不是我的方式,因为try/catch不是真正惯用的Scala,你的错误类型被丢弃了。下一个选项是使用scala.util.Try

def save(user: User)(implicit session: Session): Try[User] = Try {
  val newId = (users returning users.map(_id) += user
  user.copy(id = newId)
}

这里的代码更简单。如果Try的正文成功,则save将返回Success[User],如果不成功,则会返回Failure中包含的异常。这将允许您使用Try执行许多操作。

你可以模式匹配:

save(user) match {
   case Success(user) => Ok(user)
   case Failure(t: PSQLException) if(e.getSQLState == "23505") => InternalServerError("Some sort of unique key violation..")
   case Failure(t: PSQLException) => InternalServerError("Some sort of psql error..")
   case Failure(_) => InternalServerError("Something else happened.. it was bad..")
}

您可以像Option

一样使用它
save(user) map { user =>
   Ok(user)
} getOrElse {
   InternalServerError("Something terrible happened..")
}

你可以一次组合很多,并在第一次失败时停止:

(for {
   u1 <- save(user1)
   u2 <- save(user2)
   u3 <- save(user3)
} yield {
  (u1, u2, u3)
}) match {
   case Success((u1, u2, u3)) => Ok(...)
   case Failure(...) => ...
}

答案 1 :(得分:15)

在Slick 3.x中,您可以使用asTry

我正在使用MySQL,但PostgreSQL可以使用相同的代码,只有例外情况不同。

import scala.util.Try
import scala.util.Success
import scala.util.Failure

db.run(myAction.asTry).map {result =>  

  result match {

    case Success(res) => 
      println("success")
      // ...

    case Failure(e: MySQLIntegrityConstraintViolationException) => {
      //code: 1062, status: 23000, e: Duplicate entry 'foo' for key 'name'
      println(s"MySQLIntegrityConstraintViolationException, code: ${e.getErrorCode}, sql status: ${e.getSQLState}, message: ${e.getMessage}")
      // ...
    }

    case Failure(e) => {
      println(s"Exception in insertOrUpdateListItem, ${e.getMessage}")
      // ...
    }
  }
}

注意:也可以映射操作(myAction.asTry.map ...)而不是db.run返回的未来。