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使用逻辑存在错误
- DDL语句(如
CREATE TABLE)应该调用executeUpdate(),而非executeQuery()——后者仅适用于SELECT这类查询语句,用来返回ResultSet,用在创建表的场景完全不符合逻辑。 - 将
PreparedStatement包装成Resource不合理:你在yield阶段调用rs.executeQuery(),但Resource会自动关闭PreparedStatement,这会导致ResultSet提前失效,不过这不是连接代码未执行的直接原因。 - 此处的Mutex完全多余,每个Resource实例都会创建独立的Mutex和Connection,不存在共享资源需要同步的场景,属于不必要的复杂度。
- DDL语句(如
目录不存在可能导致静默失败
如果/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
相关产品推荐
相关产品推荐

