通过 anorm 生成 sql 查询,除一个以外的所有空值

Generate sql query by anorm, with all nulls except one

我使用 play framework 2.3.8 和 scala 开发 Web 应用程序,后端和前端的架构都很复杂。作为后端,我们使用 MS SQL,带有许多存储过程,并通过 anorm 调用它。这是问题之一。

我需要更新数据库中的一些字段。前端调用play framework,接收字段名称和值。然后我解析,字段名,然后我需要生成 SQL 更新字段的查询。我需要为所有参数分配 null,接收参数除外。我尝试这样做:

def updateCensusPaperXX(name: String, value: String, user: User) = {
  DB.withConnection { implicit c =>
    try {
        var sqlstring = "Execute [ScXX].[updateCensusPaperXX] {login}, {domain}"
        val params = List(
          "fieldName1",
          "fieldName2",
          ...,
          "fieldNameXX"
        )
        for (p <- params){
          sqlstring += ", "
          if (name.endsWith(p))
            sqlstring += value
          else
            sqlstring += "null"

        }
        SQL(sqlstring)
          .on(
            "login" -> user.login,
            "domain" -> user.domain,
          ).execute()
    } catch {
      case e: Throwable => Logger.error("update CensusPaper04 error", e)
    }
  }
}

但实际上这并不适用于所有情况。例如,当我尝试保存字符串时,它给我一个错误,如:

com.microsoft.sqlserver.jdbc.SQLServerException: Incorrect syntax near 'some phrase'

生成 sql 查询的最佳方式是什么

之所以会出现这种情况,是因为将字符串值直接写入SQL语句时,需要用引号引起来。解决此问题的一种方法是确定哪些字段是字符串并添加条件逻辑以确定是否引用该值。这可能不是最好的方法。作为一般规则,您应该使用命名参数而不是使用参数值构建字符串。这有一些好处:

  1. 您可能更容易诊断问题,因为您会在运行时收到更明智的错误消息。
  2. 它可以防止 SQL 注入的可能性。
  3. 虽然在存储过程调用的情况下这可能并不多,但您可以获得重用准备好的语句的通常性能优势。

这意味着您应该像处理用户和域一样将字段列表视为命名参数。这可以通过对上面的代码进行一些小的更改来完成。首先,您可以按如下方式构建 SQL 语句:

    val params = List(
      "fieldName1",
      "fieldName2",
      ...,
      "fieldNameXX"
    )

    val sqlString = "Execute [ScXX].[updateCensusPaperXX] {login}, {domain}," +
      params.map("{" + _ + "}").mkString{","}

上面发生的事情是您不需要直接插入值,因此您可以通过将参数列表添加到查询字符串的末尾来构建字符串。

然后您就可以开始构建您的参数列表了。请注意,SQLon 方法的参数是 NamedParameter 的可变参数列表。基本上,我们需要创建包含 "login"、"domain" 和您要填充的字段列表的 NamedParameters 序列。像下面这样的东西应该可以工作:

    val userDomainParams: Seq[NamedParameter] = (("login",user.login),("domain",user.domain))
    val additionalParams = params.map(p => 
      if (name.endsWith(p)) 
        NamedParameter(p, value)
      else 
        NamedParameter(p, None)
    ).toSeq
    val fullParams = userDomainParams ++ additionalParams
    // At this point you can execute as follows
    SQL(sqlString).on(fullParams:_*).execute()

此处发生的事情是您构建参数列表,然后使用 splat 运算符 :_* 将序列扩展为 on 方法所需的可变参数。注意上面NamedParameter中使用的None被Anorm转换成了jdbcNULL

这解决了与字符串相关的问题,因为您不再将字符串直接写入查询,而且它还有一个额外的好处,即消除与编写 SQL 字符串而不是使用参数相关的其他问题。