Scala Slick - 确定哪个 DBIO 在 DBIO.sequence 中失败
Scala Slick - Identify which DBIO is failing in DBIO.sequence
我按以下方式在数据库上执行 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
中的哪一个导致错误来制定逻辑(我不想依赖SQLException
).
我希望能够做类似的事情
.recover {
case ex: ImportError.CauseFirst => ...
case ex: ImportError.CauseSecond => ...
case ex: ImportError.CauseThird => ...
...
}
如果你使用 .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)
}
它保留 success/failure 结果触发原子事务的中止请求。
最后只是把failure中包含的error映射出来而已,确实实现一个函数来做那个还是蛮有用的
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 => ...
...
}
我按以下方式在数据库上执行 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
中的哪一个导致错误来制定逻辑(我不想依赖SQLException
).
我希望能够做类似的事情
.recover {
case ex: ImportError.CauseFirst => ...
case ex: ImportError.CauseSecond => ...
case ex: ImportError.CauseThird => ...
...
}
如果你使用 .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)
}
它保留 success/failure 结果触发原子事务的中止请求。 最后只是把failure中包含的error映射出来而已,确实实现一个函数来做那个还是蛮有用的
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 => ...
...
}