Подтвердить что ты не робот

Как поймать пятнистые исключения postgres для дублирования ключевых значений

Моя таблица имеет уникальный индекс в паре столбцов в моей базе данных 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.

4b9b3361

Ответ 1

Ваш метод 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(...) => ...
}

Ответ 2

В 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.