将模式名称作为变量传递给查询的最佳方法

Posted

技术标签:

【中文标题】将模式名称作为变量传递给查询的最佳方法【英文标题】:Best way to pass the schema name as a variable to a query 【发布时间】:2015-03-13 08:50:48 【问题描述】:

我有一个 PlayFramework 服务器(使用 Anorm),它针对具有多个架构的数据库进行操作,所有架构都具有相同的表。

我的大部分“访问数据库”函数看起来像:

def findById(zoneName: String, id: Long): Option[Employee] = 
    DB.withConnection  implicit connection =>
      SQL("""select *
          from """+zoneName+"""employee
          where employee._id = id"""
      .on(
          '_id -> id
        ).as(simpleParser.singleOpt)
    
  

但我知道这是一种错误的方法,因为它不是 SQL 注入安全的,而且在每个函数中编写当然很乏味。

我想使用字符串插值来纠正这个问题,它适用于我的 id 变量,但不适用于 zoneName

def findById(zoneName: String, id: Long): Option[Employee] = 
    DB.withConnection  implicit connection =>
      SQL"""select *
          from $zoneName.employee
          where employee._id = 1"""
      .as(simpleParser.singleOpt)
    
  

给我:

info] ! @6lenhal6c - Internal server error, for (GET) [/limbo/br/employee/1] ->
[info] 
[info] play.api.Application$$anon$1: Execution exception[[PSQLException: ERROR: syntax error at or near «$1»
[info]   Position: 25]]
[info]  at play.api.Application$class.handleError(Application.scala:296) ~[play_2.11-2.3.8.jar:2.3.8]
[info]  at play.api.DefaultApplication.handleError(Application.scala:402) [play_2.11-2.3.8.jar:2.3.8]
[info]  at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$3$$anonfun$applyOrElse$4.apply(PlayDefaultUpstreamHandler.scala:320) [play_2.11-2.3.8.jar:2.3.8]
[info]  at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$3$$anonfun$applyOrElse$4.apply(PlayDefaultUpstreamHandler.scala:320) [play_2.11-2.3.8.jar:2.3.8]
[info]  at scala.Option.map(Option.scala:146) [scala-library-2.11.5.jar:na]
[info] Caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near «$1»
[info]   Position: 25
[info]  at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2198) ~[postgresql-9.3-1102.jdbc4.jar:na]
[info]  at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1927) ~[postgresql-9.3-1102.jdbc4.jar:na]
[info]  at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255) ~[postgresql-9.3-1102.jdbc4.jar:na]
[info]  at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:561) ~[postgresql-9.3-1102.jdbc4.jar:na

也使用$zoneName 进行了测试,结果相同。

任何有关如何编写此内容的帮助或建议将不胜感激,在此先感谢您!

【问题讨论】:

您可以设置search_path 以包含您在调用函数之前“当前”处理的架构。或者,如果您使用不同的数据库用户,则将该用户的默认 search_path 设置为该用户应使用的架构名称。 我考虑过这个选项,但我放弃了它,因为将来我需要支持超过 1 个模式的查询 那么我认为除了在运行时连接正确的模式之外,您别无选择。老实说,这个设置看起来确实有点奇怪。您试图通过在不同模式中使用“相同”表来解决什么问题?也许有更好的方法来解决那个 也许有更好的解决方案,是的。我所做的是让几家公司(或同一家公司的分支机构)使用同一个数据库。我的服务器是访问这些数据的 RESTful 提供程序。通过这种方式,我将数据从一家公司保存到另一家公司,但我保留了进行暗示多个模式的查询的可能性。正如您所建议的,也许一种方法是让 db 用户使用 search_path 并使用正确的用户进行查询?还是保留分开的数据库? 你提前知道哪些查询会是跨模式的吗?如果这样做,您可以在“默认”模式中创建视图,以结合来自其他模式的信息。这样,您可以更改请求中的 search_path 以匹配当前租户,而不必担心在单个查询中访问来自不同架构的对象。 【参考方案1】:

使用 Anorm 字符串插值,任何$expression 都将被提供一个参数,也就是说,如果它是一个字符串,它将被 JDBC 驱动程序引用。

如果您想用字符串(例如动态模式)替换部分 SQL 语句,您可以使用连接,或者从最新版本(2.4.0-M3 或 2.3.8)开始使用语法 #$expr

val table = "myTable"
SQL"SELECT * FROM #$table WHERE id=$id"
// SELECT * FROM myTable WHERE id=?

【讨论】:

我将测试#$expr 方式,我不知道这种语法,看起来不错。你知道这是否只是替换字符串?在这种情况下,它不会解决我的 SQL 注入问题。我在哪里可以找到这方面的文档? 这种语法只允许将标准字符串插值与异常参数插值混合。标准字符串插值不会阻止 SQL 注入。

以上是关于将模式名称作为变量传递给查询的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

将带有变量的闭包传递给 Laravel 查询构建器中的 where 方法

如何使用 SQLAlchemy 将查询结果传递给变量(作为字符串)? [复制]

将变量名称的向量传递给 dplyr 中的arrange()

PHP MVC 最佳实践 - 将 Session 变量从控制器传递给模型类或直接在模型中访问

如何将变量传递给 oracle 中的 min 和 max 函数?

以整齐的方式将多列作为分组变量传递给 UDF