繁体   English   中英

使用预处理语句设置表名

[英]Using Prepared Statements to set Table Name

我正在尝试使用准备好的语句将表名设置为 select 来自的数据,但是在执行查询时我一直收到错误。

错误和示例代码显示如下。

[Microsoft][ODBC Microsoft Access Driver] Parameter 'Pa_RaM000' specified where a table name is required.



private String query1 = "SELECT plantID, edrman, plant, vaxnode FROM [?]"; //?=date
public Execute(String reportDate){
    try {

        Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
        Connection conn = DriverManager.getConnection(Display.DB_MERC);
        PreparedStatement st = conn.prepareStatement(query1);
        st.setString(1, reportDate);
        ResultSet rs = st.executeQuery();

关于可能导致此问题的任何想法?

表名不能用作参数。 它必须是硬编码的。 因此,您可以执行以下操作:

private String query1 = "SELECT plantID, edrman, plant, vaxnode FROM [" + reportDate + "?]";

这在技术上是可行的,但有一种解决方法,但做法非常糟糕。

String sql = "IF ? = 99\n";
sql += "SELECT * FROM first_table\n";
sql += "ELSE\n";
sql += "SELECT * FROM second_table";
PreparedStatement ps = con.prepareStatement(sql);

然后当你想从 first_table 中选择时,你设置参数

ps.setInt(1, 99);

或者,如果没有,您将其设置为其他内容。

如果您需要一个不易受到 SQL 注入攻击的解决方案,您必须为您需要的所有表复制查询:

final static String QUERIES = {
    "SELECT x FROM Table1 x WHERE a=:a AND b=:b AND ...",
    "SELECT x FROM Table2 x WHERE a=:a AND b=:b AND ...",
    "SELECT x FROM Table3 x WHERE a=:a AND b=:b AND ...",
    ...
};

是的:查询是重复的,只有表名不同。

现在您只需选择适合您的表格的查询,例如

...
PreparedStatement st = conn.prepareStatement(QUERIES[index]);
...

您可以使用这种方法至 JPA、Hibernate 等等...

如果您想要更详细的方法,请考虑使用枚举

enum AQuery {
    Table1("SELECT x FROM Table1 x WHERE a=:a AND b=:b AND ..."),
    Table2("SELECT x FROM Table2 x WHERE a=:a AND b=:b AND ..."),
    Table3("SELECT x FROM Table3 x WHERE a=:a AND b=:b AND ..."),
    ...

    private final String query;
    AQuery(final String query) {
        this.query = query;
    }

    public String getQuery() {
        return query;
    }
}

现在使用任一索引

String sql = AQuery.values()[index].getQuery();
PreparedStatement st = conn.prepareStatement(sql);
...

或者使用表名

String sql = AQuery.valueOf("Table1").getQuery();
PreparedStatement st = conn.prepareStatement(sql);
...

正如许多人所说,您不能将语句参数用于表名,只能用于作为条件一部分的变量。

基于您有一个带有(至少)两个表名的可变表名的事实,也许最好创建一个方法来获取您正在存储的实体并返回一个准备好的语句。

PreparedStatement p = createStatement(table);

您不能在准备好的语句中设置表名

如前所述,无法使用 preparedStatement.setString(1, tableName) 在准备好的语句中设置表名。 并且也不可能将 SQL 查询的部分内容添加到准备好的语句中(例如 preparedStatement.addSql(" or xyz is null") )。

如何在不冒 SQL 注射风险的情况下正确地做事?

表名必须插入到您要使用字符串操作执行的 SQL(或 JQL)查询中,例如"select * from " + tableNameString.format("select * from %s", tableName)

但是如何避免SQL注射呢?

如果表名不是来自用户输入,您可能是安全的。 例如,如果你做出像这里这样的决定

String tableName;
if(condition) {
    tableName = "animal";
} else {
    tableName = "plant";
}
final String sqlQuery = "delete from " + tableName;
...

如果表名依赖于用户输入,则需要手动检查输入。 例如,使用包含所有有效表名的白名单

if(!tableNamesWhitelist.contains(tableName)) {
    throw new IllegalArgumentException(tableName + " is not a valid table name");
}
String sqlQuery = "delete from " + tableName;

或使用枚举

public enum Table {
    ANIMAL("animal"),
    PLANT("plant");

    private sqlTableName;
    private TableName(String sqlTableName) {
        this.sqlTableName= sqlTableName;
    }
    public getSqlTableName() {
        return sqlTableName;
    }
}

然后将用户输入的字符串(如ANIMAL )转换为Table.ANIMAL 如果不存在合适的枚举值,则抛出异常。

例如

@DeleteMapping("/{table}")
public String deleteByEnum(@PathVariable("table") Table table) {
    final String sqlQuery = "delete from " + table.getSqlTableName();
    ...
}

当然,这些示例也适用于 select、更新等,还有许多其他检查用户输入的实现也是可能的。

这可能有帮助:

public ResultSet getSomething(String tableName) {

PreparedStatement ps = conn.prepareStatement("select * from \`"+tableName+"\`");
ResultSet rs = ps.executeQuery();
}

我不确定您是否可以使用 PreparedStatement 来指定表的名称,只是某些字段的值。 无论如何,您可以尝试相同的查询,但没有括号:

"SELECT plantID, edrman, plant, vaxnode FROM ?"
String table="pass"; 

String st="select * from " + table + " ";

PreparedStatement ps=con.prepareStatement(st);

ResultSet rs = ps.executeQuery();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM