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

Resource.fromAutoCloseable未执行内部操作:Cats Effect数据库创建失败排查

问题:Cats Effect中数据库连接代码未执行的原因分析

我尝试用Cats Effect创建SQLite数据库并生成数据表,编写的代码如下:

def createTable[F[_]: Async]: Resource[F, ResultSet] = {

  val createTableQuery = "CREATE TABLE IF NOT EXISTS warehouses (\n"
              + " id integer PRIMARY KEY,\n"
              + " name text NOT NULL,\n"
              + " capacity real\n"
              + ");";
  for {
    mutex <- Resource.eval(Mutex[F])
    connection <- Resource.fromAutoCloseable(Async[F].blocking {
      DriverManager.getConnection(s"jdbc:sqlite:/tmp/db/test.sqlite")
    })
    rs <-
      Resource
        .fromAutoCloseable {
          mutex.lock.surround {
            Async[F].blocking {
              connection.prepareStatement(
                createTableQuery
              )
            }
          }
        }
  }yield{
    rs.executeQuery()
  }

}

但数据库未成功创建,排查发现DriverManager.getConnection(s"jdbc:sqlite:/tmp/db/test.sqlite")代码未执行,调用代码如下:

createTable[IO].use{
  rs => IO(rs)
}

原因分析

  • 惰性求值导致IO未执行
    Cats Effect的IO是惰性计算模型,你写的createTable[IO].use(rs => IO(rs))只是定义了一个计算流程的描述,并没有真正触发代码执行。必须通过unsafeRunSync()、unsafeRunAsync()或者在IOApp的run方法中启动这个IO,才能让代码实际运行起来。

  • Resource使用逻辑存在错误

    1. DDL语句(如CREATE TABLE)应该调用executeUpdate(),而非executeQuery()——后者仅适用于SELECT这类查询语句,用来返回ResultSet,用在创建表的场景完全不符合逻辑。
    2. 将PreparedStatement包装成Resource不合理:你在yield阶段调用rs.executeQuery(),但Resource会自动关闭PreparedStatement,这会导致ResultSet提前失效,不过这不是连接代码未执行的直接原因。
    3. 此处的Mutex完全多余,每个Resource实例都会创建独立的Mutex和Connection,不存在共享资源需要同步的场景,属于不必要的复杂度。
  • 目录不存在可能导致静默失败
    如果/tmp/db目录不存在,SQLite不会自动创建目录,仅会创建文件。此时DriverManager.getConnection会抛出异常,但由于你未执行IO,无法看到错误信息,从而误以为连接代码未运行。

修正后的示例代码

import cats.effect.{Async, IO, IOApp, Resource}
import java.sql.DriverManager

object DatabaseSetup extends IOApp {
  def createTable[F[_]: Async]: Resource[F, Unit] = {
    val createTableQuery = """CREATE TABLE IF NOT EXISTS warehouses (
                             |  id integer PRIMARY KEY,
                             |  name text NOT NULL,
                             |  capacity real
                             |);""".stripMargin

    // 确保目标目录存在,解决SQLite路径问题
    val ensureDir = Async[F].blocking {
      new java.io.File("/tmp/db").mkdirs()
    }

    Resource.eval(ensureDir) *>
      Resource.fromAutoCloseable(Async[F].blocking {
        DriverManager.getConnection("jdbc:sqlite:/tmp/db/test.sqlite")
      }).flatMap { connection =>
        Resource.eval(Async[F].blocking {
          val stmt = connection.prepareStatement(createTableQuery)
          try stmt.executeUpdate() finally stmt.close()
        })
      }
  }

  override def run(args: List[String]): IO[ExitCode] = {
    createTable[IO]
      .use(_ => IO.println("数据表创建成功"))
      .as(ExitCode.Success)
  }
}

内容的提问来源于stack exchange,提问作者Hima_93

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 17:03:30