scala chaining Trys与托管资源最终需要/ close()

时间:2013-08-06 03:14:55

标签: scala functional-programming monads

对于练习来说,我已经从100行直到这里得到了一些简单的JDBC内容,但它不会出现问题。有任何想法吗?更好的方法?

def withResultSet[T](sql: String, f: ResultSet => T)(implicit info: ConnectionInfo): Try[T] = {
    for {
      conn <- Try(connect(info))
      stmt <- Try(conn.createStatement()) orElse { case err: SQLException => {conn.close(); err} }
      results <- Try(stmt.executeQuery(sql)) orElse { case err: SQLException => { conn.close(); stmt.close(); err }}
    } yield f(results)
  }

我的错误是

 missing parameter type for expanded function
The argument types of an anonymous function must be fully known. (SLS 8.5)
Expected type was: scala.util.Try[?]
      stmt <- Try(conn.createStatement()) orElse { case err: SQLException => {conn.close(); err} }
                                                 ^

3 个答案:

答案 0 :(得分:3)

我不知道Try是不再需要资源时处理资源的正确工具。至少我看不出一个明显的方法来做到这一点。您可能需要查看https://github.com/jsuereth/scala-arm。然后你的代码可能如下所示:

def withResultSet[T](sql: String, f: ResultSet => T)(
                     implicit info: ConnectionInfo): ManagedResource[T] = for {
  conn <- managed(connect(info))
  stmt <- managed(conn.createStatement()) 
  results <- managed(stmt.executeQuery(sql))
} yield f(results)

您可以继续使用map或flatMap(或用于理解)来处理其他可能的资源,最后您可以从中获取OptionEither,同时关闭一切都需要关闭。

答案 1 :(得分:2)

您可以将函数拆分为不同的部分,每个部分对应一种资源类型,并将其与单独的资源管理器特征一起使用。

通用资源管理器(也可以与文件等一起使用):

trait ResourceManager {
  def withResource[T <: {def close()}, R](resource: T)(code: (T) => R): R = {
    try {
      code(resource)
    } finally {
      import scala.language.reflectiveCalls
      resource.close()
    }
  }

混合成这样的东西

class Db extends ResourceManager {
  private def getConnection = ...

  def withConnection[T](f: (Connection) => T): T = {
    withResource(getConnection) {
      conn =>
        f(conn)
    }
  }

  def withStatement[T](f: (Statement) => T): T = {
    withConnection {
      conn =>
        withResource(conn.createStatement()) {
          stmnt =>
            f(stmnt)
        }
    }
  }

  def withResultSet[T](selectSqlCmd: String)(f: (ResultSet) => T): T = {
    withStatement {
      stmnt => {
        withResource(stmnt.executeQuery(selectSqlCmd)) {
          rs =>
            f(rs)
        }
      }
    }
  }
}

堆叠资源,并在每个级别给出入口点。

在另一个级别之上

  def mapResultSet[T, C <: Iterable[T]](selectSqlCmd: String)
                              (f: (ResultSet) => T)
                              (implicit cbf: CanBuildFrom[Nothing, T, C]): C = {
    withResultSet(selectSqlCmd) {
      rs =>
        val builder = cbf()
        while (rs.next()) {
          builder += f(rs)
        }
        builder.result()
    }
  }

每种方法只向下走一步,for理解被分解为单独的函数,Try不会干扰。

像这样使用:

val db = new Db()
val result = db.mapResultSet("select * from pg_user")(rs => rs.getString(1))

从一行中读取数据库。

答案 2 :(得分:1)

recoverWith是我需要的;你实际上并不需要恢复,你可以重新抛出。

def withResultSet[T](sql: String, f: ResultSet => T)(implicit info: ConnectionInfo): Try[T] = {
    for {
      conn <- Try(connect(info))
      stmt <- Try(conn.createStatement()) recoverWith { case err => {conn.close(); Failure(err)} }
      results <- Try(stmt.executeQuery(sql)) recoverWith { case err => { conn.close(); stmt.close(); Failure(err) }}
    } yield f(results)
  }