问题描述
我通过以下方式在数据库上事务性地执行 3 个不同的操作
// firstDBIO,secondDBIOA,thirdDBIO: DBIOAction[Unit]
F.delay {
val unitOfWork = DBIO.sequence(
List(
firstDBIO,secondDBIO,thirdDBIO,),)
db.run(unitOfWork.transactionally)
}.futureLift.void.map(_.asRight[ImportError]).recover {
case ex: sqlException => Left(ImportError.UnexpectedError)
}
这可以正常工作,但是,当事务失败时,在 recover
中,我无法根据 DBIO
中的哪个导致错误来制定逻辑(我不想依赖 { {1}})。
我希望能够做类似的事情
sqlException
解决方法
如果您使用 .sequence
,那么您只会在第一个失败的未来失败。您有 2 个选择:
- 映射每个 DBIO 的错误以包含数字 - 我猜您可能会滥用
.cleanUp
方法,例如dbio.cleanUp({ case Some(error) => DBIO.failed(improveError(error)) // add idx to Exception or sth case None => DBIO.successful(()) },keepFailure = false)
- 将单个结果保留为
Try
并在交易后解决它们dbio.asTry // then use db.run(DBIO.sequence(dbios).transactionally) // to get Future[List[Try[Int]]]
与前者相比,我不确定后者将如何处理事务和回滚,但两种情况都会让您找出哪个操作失败了。
,我发现这个解决方案正是我想要的
dbio.asTry.flatMap {
case Success(v) => DBIO.successful(v)
case Failure(e) => DBIO.failed(ImportError.CauseFirst)
}
它保留了触发原子事务中止的成功/失败结果。 最后,它只是映射包含在失败中的错误,确实实现一个函数来做到这一点非常有用
implicit class DBIOOps[A](dbio: DBIO[A]) {
def mapFailure(f: Throwable => E with Throwable) = dbio.asTry.flatMap {
case Success(v) => DBIO.successful(v)
case Failure(e) => DBIO.failed(f(e))
}
}
F.delay {
val unitOfWork = DBIO.sequence(
List(
firstDBIO.mapFailure(_ => ImportError.CauseFirst),secondDBIO.mapFailure(_ => ImportError.CauseSecond),thirdDBIO.mapFailure(_ => ImportError.CauseThird),...
),)
db.run(unitOfWork.transactionally)
}.futureLift.void.map(_.asRight[ImportError]).recover {
case ex: ImportError.CauseFirst => ...
case ex: ImportError.CauseSecond => ...
case ex: ImportError.CauseThird => ...
...
}