Java PreparedStatement SQL语法错误

3

这是一个非常奇怪的错误,今天才开始出现。当我使用带有参数?的预处理语句时,会出现错误,但是当我不使用参数时,它可以正常工作。 以下是导致错误的代码:

    String table = "files";
    Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
    PreparedStatement prep = conn.prepareStatement("SELECT * FROM ?");
    prep.setString(1, table);
    ResultSet rs = prep.executeQuery();

    while(rs.next()) {
        System.out.println(rs.getString("file_name"));
    }

这会产生以下错误:
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''files'' at line 1

另外,将其更改为以下内容也可以正常工作:

    String table = "files";
    Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
    PreparedStatement prep = conn.prepareStatement("SELECT * FROM " + table);
    ResultSet rs = prep.executeQuery();

    while(rs.next()) {
        System.out.println(rs.getString("file_name"));
    }

这似乎没有太多意义。有什么想法吗?


在另一张表上尝试了一下,结果更奇怪了。 这个可以正常工作并正确记录管理员登录:

    String sql = "SELECT * FROM " + ADMIN_AUTH_TABLE + " WHERE " + column + " = '" + hashedPassword + "'";
    PreparedStatement prepared = connection.prepareStatement(sql);

以下代码不会导致错误,但会返回一个信息,说输入的密码是不正确的(但它是正确的——我已经再三确认过)。
    String sql = "SELECT * FROM " + ADMIN_AUTH_TABLE + " WHERE ? = ?";
    PreparedStatement prepared = connection.prepareStatement(sql);
    prepared.setString(1, column);
    prepared.setString(2, hashedPassword);


明白了:使用 ? 代替值。 此外,这里的答案有所帮助。


1
我认为你不能使用预处理语句将表名作为参数传递,因为预处理语句是预编译的,需要在编译时知道表和/或视图。 - Leonz
你只需要通过在查询中添加表名进行字符串拼接即可轻松完成更改,因此它能够正常工作。 - Leonz
2个回答

3

在SQL语句中,绑定参数不能用于标识符。只有可以通过绑定占位符提供。

这样做是可行的:

SELECT foo FROM bar WHERE id = ? 

这样做是行不通的,因为表名是一个标识符。
SELECT foo FROM ? WHERE id = 2

您不能提供列名,因为列名也是标识符。类似这样的语句会运行,但可能不会产生您期望的结果。

SELECT ? AS foo FROM bar WHERE ? = 0

如果我们为两个占位符都提供'foo'的值,那么查询实际上相当于包含两个字符串字面量的查询:

SELECT 'foo' AS foo FROM bar WHERE 'foo' = 0

MySQL会执行该语句,因为它是有效的语句(如果表bar存在且我们对其有权限)。该查询将返回bar中的每一行(因为WHERE子句中的谓词评估为TRUE,独立于表的内容... 然后我们得到常量字符串foo

这个字符串foo恰好与我们的表中的列名匹配并不重要。


这个限制与SQL优化器的操作方式有关。我们不需要深入探讨所有步骤的细节(简要地:解析标记、执行语法检查、执行语义检查、确定查询计划,然后执行查询计划)。

所以这里是简短的故事:绑定参数的值在那个过程中太晚提供了。它们直到最后一步——执行查询计划才被提供。

优化器需要在较早的阶段知道引用哪些表和列...用于语义检查和制定查询计划。表和列必须对优化器进行“标识”。绑定占位符在需要表名和列名的时候还是“未知”的。

(这个短故事并不完全准确;不要把所有的都当作福音。但它确实解释了绑定参数不能用于标识符(如表名和列名)的原因。)

tl;dr

考虑到您运行的特定语句,唯一可以作为绑定参数传递的是“hashedPassword”值。该语句中的其他所有内容都必须在SQL字符串中。

例如,像这样的东西将起作用:

String sqltext = "SELECT * FROM mytable WHERE mycolumn = ?";
PreparedStatement prepared = connection.prepareStatement(sqltext);
prepared.setString(1, hashedPassword);

如果要使SQL语句的其他部分“动态”(例如表名和列名),则需要在Java代码中处理(使用字符串连接)。该字符串的内容需要最终类似于将sqltext字符串(在我的示例中)传递给prepareStatement方法时的内容。


1
PreparedStatement的参数应该只应用于可以用于条件子句中的参数。表名在这里不适用。
如果您有一个select语句,其中表名可以应用于条件子句中,则可以这样做,否则不能。

3
可以在select子句、on子句、having子句、offset子句等任何允许存在的地方使用。 - Andreas
你是对的,@Andreas。条件从句更好听一些。谢谢。 - josivan
这个答案并不完全准确。绑定占位符实际上可以出现在很多不同的地方。限制实际上是关键字和标识符不能作为绑定参数提供。原因是绑定参数的值在语句处理过程中被提供得太晚了。在语法检查期间,这些值是未知的,此时必须知道关键字。在执行语义检查时,表、列、函数等必须被识别,而这些值也是未知的。 - spencer7593

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接