Scala、Slick与Cats:如何用OptionT映射不同SQL错误?
解决Slick查询中数据库异常的多错误映射问题
你的问题核心在于没分清OptionT处理的是查询结果为空(None)的场景,而数据库的约束异常是在database.run执行时就会触发Future失败,根本不会走到OptionT的流程里。所以不能在.toRight里处理数据库异常,得先在Future层面捕获异常,再处理空值情况。
正确的实现方式
首先定义PostgreSQL约束对应的SQL状态常量(这类错误码是SQL标准定义的,外键约束为23503、唯一约束为23505):
import org.postgresql.util.PSQLException // PostgreSQL约束错误SQL状态码 val FOREIGN_KEY_CONSTRAINT_VIOLATION = "23503" val UNIQUE_CONSTRAINT_VIOLATION = "23505"
然后修改方法,分两步处理异常和空值:
def method(): Future[Either[Error, MyCustomDTO]] = { database.run(query) // 第一步:捕获数据库执行时的异常,映射为自定义Error .recover { case e: PSQLException => e.getSQLState match { case FOREIGN_KEY_CONSTRAINT_VIOLATION => Left(constraintError()) case UNIQUE_CONSTRAINT_VIOLATION => Left(uniqueError()) case _ => Left(dataNotFound()) // 其他数据库异常返回通用错误 } } // 第二步:处理查询结果的Option,映射为DTO或空值错误 .flatMap { case Some(entity) => Future.successful(Right(MyCustomDTO(entity))) case None => Future.successful(Left(dataNotFound())) } }
用OptionT简化链式调用(可选)
如果想保留OptionT的链式风格,可以结合Future的recover和最终结果的异常捕获:
def method(): Future[Either[Error, MyCustomDTO]] = { OptionT( database.run(query) .recover { case e: PSQLException => // 将异常转换为自定义Error后抛出,后续在value阶段捕获 throw e.getSQLState match { case FOREIGN_KEY_CONSTRAINT_VIOLATION => constraintError() case UNIQUE_CONSTRAINT_VIOLATION => uniqueError() case _ => dataNotFound() } } ) .map(MyCustomDTO(_)) .toRight(dataNotFound()) .value // 捕获之前抛出的自定义Error,转为Left结果 .recover { case err: Error => Left(err) } }
关键说明
.toRight(dataNotFound())仅处理查询结果为None的场景,和数据库执行异常完全无关;- 数据库约束异常属于Future的Failure分支,必须用
recover/recoverWith在Future层面捕获; - PSQLException是PostgreSQL驱动的特定异常,需确保依赖中包含PostgreSQL驱动包并正确导入。
内容的提问来源于stack exchange,提问作者Developus
相关产品推荐
相关产品推荐

