【问题标题】:scala anorm foreach without loading everything into memoryscala anorm foreach 不将所有内容加载到内存中
【发布时间】:2017-11-17 16:00:54
【问题描述】:

以下代码使用 scala anorm 打印所有行:

import anorm._

val query = s"select col_str, col_num from mytable"

val rowParser: RowParser[~[String, Int]] = SqlParser.str(1) ~ SqlParser.int(2)

def f(row: (String, Int)) {
  println(row._1 + "\t" + row._2)
}

val rows: List[(String, Int)] = SQL(query).as(rowParser.*).map(SqlParser.flatten)

rows.foreach(f)

但是它需要将所有数据加载到内存中。避免加载所有数据的解决方案是使用fold,如下:

SQL(query).fold(Unit, ColumnAliaser.empty) { (_, r: Row) =>
  println(r[String](1) + "\t" + r[Int](2))
  Unit
}

但是,这里我没有使用rowParserflatten。如何修改前面的代码以使用rowParserflatten,而不将所有内容加载到内存中?像这样的东西(注意:这段代码不起作用):

SQL(query).as(rowParser.*).map(SqlParser.flatten).fold(Unit, ColumnAliaser.empty) { (_, row: (String, Int)) =>
  f(row)
  Unit
}

而且更困难的是,必须创建一个隐式的forEach2 函数,以便我可以按如下方式运行它:

SQL(query).as(rowParser.*).map(SqlParser.flatten).forEach2(f)

我之前尝试过的一些代码:

def foreach[T, A, B](sqlQuery: SqlQuery, rowParser: RowParser[~[A, B]], f: (~[A, B]) => T) {
  val result: Either[List[Throwable], Unit.type] = sqlQuery.fold(Unit, ColumnAliaser.empty) { (_, row: Row) =>
    rowParser(row) match {
      case Success(r: ~[A, B]) =>
        f(r)
        Unit
      case Error(err) =>
        throw AnormException(err.toString)
    }
  }

  result.left.foreach { t: Seq[Throwable] =>
    t.foreach(_.printStackTrace)
    t.headOption.foreach { tt => throw tt}
  }
}

def f(row: ~[String, Int]) {
  println(row._1 + "\t" + row._2)
}

foreach(SQL(query), rowParser, f)

这行得通。但是,我需要将def f(row: (String, Int)) 转换为def f(row: ~[String, String])。如何在我的 f 函数中删除此 ~?这个foreach 函数也需要一行有两列。如何将其推广到 n 列?

【问题讨论】:

  • 可以看一下streaming results的文档(Anorm提供了一个Akka Stream模块)
  • 我在发布问题之前已经研究过此文档。
  • 就像在任何 Anorm 解析器中一样,~ 可以表示至少包含两个列的元组结构。您不能“删除”它,而是解析它。
  • 感谢信息。知道如何在不将所有数据加载到内存的情况下执行SQL(query).as(rowParser.*).map(SqlParser.flatten).foreach(println) 之类的操作吗?
  • 宁可使用.fold

标签: scala anorm


【解决方案1】:

您的foreach 的这种改进是否足够好?或者你还需要什么?

object AnormForEachOps {

  import anorm._
  import java.sql.Connection

  implicit class ForEachOps(val query: SqlQuery) extends AnyVal {
    def foreach[T1, T2, R](rowParser: RowParser[~[T1, T2]], f: R => Unit)(implicit connection: Connection, fl: TupleFlattener[(T1 ~ T2) => R]): Unit = {
      query.fold(Unit, ColumnAliaser.empty) { (_, r: Row) =>
        rowParser(r).map(SqlParser.flatten) match {
          case Success(t) =>
            f(t)
            Unit
          case Error(err) =>
            throw AnormException(err.toString)
        }
        Unit
      }
    }
  }

}

所以你的例子会变成这样:

import AnormForEachOps._
val query = s"select col_str, col_num from mytable"
val rowParser = SqlParser.str(1) ~ SqlParser.int(2)
SQL(query).foreach(rowParser, (r: (String, Int)) => {
    Logger.info(r._1 + "\t" + r._2)
})

这个修改的主要技巧是隐式的 TupleFlattener 参数,它执行从 ~ 到普通元组的转换(通过 SqlParser.flatten 调用)

【讨论】:

  • 它有效!它也适用于 3 个元素的元组(而不是 2 个)。伟大的!我也修改了它以接受 Macro.namedParser。我让 foreach 返回 List[Throwable] 以防出现错误。谢谢!
  • @DavidPortabella,它应该适用于 as(rowParser.*) 使用的任何元组,因为它使用相同的基础架构。至于错误处理,我复制了您在示例中提供的那个。显然,如果您想要其他东西,可以更改它。
猜你喜欢
  • 2012-11-17
  • 2018-11-21
  • 2021-04-14
  • 2014-05-05
  • 2014-05-19
  • 2019-11-24
  • 2012-05-13
  • 2020-08-21
  • 2012-03-31
相关资源
最近更新 更多