Doobie中无法用Either捕获SQL查询错误的问题咨询
问题分析与解决
为什么Left分支不生效?
你遇到的NPE不是在SQL查询执行阶段抛出的,而是在构建ConnectionIO实例的过程中就触发了。具体来说,sqlQuery.query[ReportSqlResponse]这行代码在定义query变量时,会立即执行Doobie的类型推导逻辑,尝试生成Read[ReportSqlResponse]实例。如果这个过程出错(比如ReportSqlResponse的字段与SQL返回列不匹配,或者Doobie无法自动生成合法的Read实例),会直接抛出NPE——这个异常发生在IO动作启动之前,根本没进入query.transact(xa).attempt的处理流程,自然无法被Either捕获,最终导致HTTP 500。
修复方案
方案1:将ConnectionIO的构建逻辑包裹在IO中
把创建query的代码放到IO.delay(或IO.apply)里,让构建阶段的异常被IO捕获,这样后续的attempt就能处理:
def executeQuery(xa: HikariTransactor[IO]): IO[List[ReportSqlResponse]] = IO.delay { val query: ConnectionIO[List[ReportSqlResponse]] = sqlQuery.query[ReportSqlResponse].to[List] query }.flatMap { query => query.transact(xa).attempt.flatMap { case Right(result) => IO.pure(result) case Left(e) => IO.println(s"Error executing query: ${e.getMessage}") *> IO.pure(List.empty[ReportSqlResponse]) } }
方案2:修复ReportSqlResponse的Read实例问题
根源是Doobie无法生成合法的Read[ReportSqlResponse]导致NPE,你需要确保:
- SQL查询返回的列名、数量与ReportSqlResponse的字段完全匹配(大小写敏感,除非配置了映射规则)
- 如果是自定义类型,手动提供
Read实例,比如:
import doobie.implicits._ import doobie.util.Read case class ReportSqlResponse(id: Int, name: String) implicit val reportRead: Read[ReportSqlResponse] = Read[(Int, String)].map { case (id, name) => ReportSqlResponse(id, name) }
补充说明
如果不修复Read实例的问题,即使捕获了异常,后续正常查询也会失败,所以优先解决类型映射的根本问题,再用IO包裹构建逻辑来处理潜在的初始化异常。
内容的提问来源于stack exchange,提问作者Dmitriy Kolesnik
相关产品推荐
相关产品推荐

