Scala 阿诺姆:什么条件,有条件的

Scala 阿诺姆:什么条件,有条件的,scala,playframework,playframework-2.4,anorm,Scala,Playframework,Playframework 2.4,Anorm,考虑这样一个存储库/DAO方法,它非常有效: def countReports(customerId: Long, createdSince: ZonedDateTime) = DB.withConnection { implicit c => SQL"""SELECT COUNT(*) FROM report WHERE customer_id = $customerId AND create

考虑这样一个存储库/DAO方法,它非常有效:

def countReports(customerId: Long, createdSince: ZonedDateTime) =
  DB.withConnection {
    implicit c =>
      SQL"""SELECT COUNT(*)
            FROM report
            WHERE customer_id = $customerId
            AND created >= $createdSince
         """.as(scalar[Int].single)
  }
但是,如果该方法是用可选参数定义的,该怎么办

def countReports(customerId: Option[Long], createdSince: Option[ZonedDateTime])
要点是,如果存在任意一个可选参数,请在过滤结果时使用它(如上所示),否则(如果是
None
),只需省略相应的WHERE条件即可

使用可选WHERE条件编写此方法的最简单方法是什么?作为一名Anorm新手,我一直在努力寻找一个这样的示例,但我认为一定有某种合理的方法可以做到这一点(即,不必为当前/缺失参数的每个组合重复SQL)

请注意,
java.time.ZonedDateTime
实例在anrom
SQL
调用中使用时,会完美地自动映射到Postgres
timestametz
。(尝试将WHERE条件提取为字符串(在
SQL
之外,使用普通字符串插值创建)不起作用;
toString
生成数据库无法理解的表示形式。)


重头戏2.4.4

一种方法是设置过滤子句,例如

val customerClause =
  if (customerId.isEmpty) ""
  else " and customer_id={customerId}"
然后将这些替换为SQL:

SQL(s"""
  select count(*)
    from report
    where true
      $customerClause
      $createdClause
""")
.on('customerId -> customerId, 
  'createdSince -> createdSince)
.as(scalar[Int].singleOpt).getOrElse(0)
我认为最好使用
{variable}
而不是
$variable
,因为它可以降低SQL注入攻击的风险,因为有人可能使用恶意字符串调用您的方法。如果SQL中没有引用其他符号(例如,如果子句字符串为空),Anorm不介意。最后,根据数据库(?),计数可能不会返回任何行,因此我使用singleOpt而不是single

我很好奇你还收到了什么其他的答案

编辑:Anorm插值(即SQL“…”,一种超越Scala的“…”、f“…”和raw“…”)的插值实现,允许使用
$variable
相当于
{variable}
.on
。从Play 2.4开始,Scala和Anorm插值可以混合使用Anorm(SQL参数/变量)的
$
和Scala(普通字符串)的
$
。事实上,只要Scala插值字符串不包含对SQL参数的引用,这种方法就可以很好地工作。在2.4.4中,我发现在使用Anorm插值时,在Scala插值字符串中使用变量的唯一方法是:

val limitClause = if (nameFilter="") "" else s"where name>'$nameFilter'"
SQL"select * from tab #$limitClause order by name"
但这很容易受到SQL注入的影响(例如,类似于
it's
的字符串将导致运行时语法异常)。因此,对于插值字符串中的变量,似乎有必要使用“传统”
。on
方法,仅使用Scala插值:

val limitClause = if (nameFilter="") "" else "where name>{nameFilter}"
SQL(s"select * from tab $limitClause order by name").on('limitClause -> limitClause)
也许在将来,Anorm插值可以扩展到解析变量的插值字符串

Edit2:我发现有些表中可能包含或不包含在查询中的属性的数量会不时变化。对于这些情况,我定义了一个上下文类,例如
CustomerContext
。在本例中,对于影响sql的不同子句,有
lazy val
s。sql方法的调用方必须提供一个
CustomerContext
,然后sql将包含
${context.createdClause}
等内容。这有助于提供一致性,因为我最终会在其他地方使用上下文(例如分页的总记录数等)

终于在我的示例中使用了这个,也使用了ZoneDateTime

def countReports(customerId: Option[Long], createdSince: Option[ZonedDateTime]) =
  DB.withConnection {
    implicit c =>
      SQL( """
          SELECT count(*) FROM report
          WHERE ({customerId} is null or customer_id = {customerId})
          AND ({created}::timestamptz is null or created >= {created})
           """)
        .on('customerId -> customerId, 'created -> createdSince)
        .as(scalar[Int].singleOpt).getOrElse(0)
  }
棘手的部分是必须在空检查中使用
{created}::timestamptz
。正如我们所说,这是围绕一个新的目标工作所必需的

显然,只有时间戳类型才需要强制转换,更简单的方法(
{customerId}为null
)可以处理其他所有内容。另外,如果您知道其他数据库是否需要类似的内容,或者这是Postgres特有的特性,请进行注释


(虽然也可以很好地工作,但正如您在完整示例中所看到的那样,这肯定更干净。)

使用Anorm插值
SQL“…”
(这不是标准的字符串插值)以相同的方式设置参数值,不再存在SQL注入的风险。谢谢,这种方法非常干净,而且确实有效!我最初没有注意到的是,您使用的是
SQL(“”)
,而不是
SQL“”
,正如我在与错误作斗争后所学到的:在“{”处或附近有一段时间的语法错误。你不能在
SQL''中使用
{customerId}
语法。
@cchantep:对于这种“条件条件”情况,我似乎需要
{customerId}
.on()
让它工作的方法。如果你愿意,你可以通过发布一个与
SQL“…”
:)相当但使用
SQL实现的答案来证明我是错的。(当然,我的意思是
SQL(s“”)
上面的
s
需要正确替换
$customerClause
等。)有趣的是,我没有看到SQL“…”格式。Tks。我在一个游戏中玩过不同的选项;请随意使用fork。