You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:04:58