### 有哪些 SQL 语句无法使用预编译的方式

1. 动态的数据库对象名称

预编译的参数只能用于替换 SQL 语句中的值（`VALUES`），而不能用于替换表名、列名、排序字段（`ORDER BY`）或数据库名

例如，你不能这样做：

    // 错误的预编译用法
    String sql = "SELECT * FROM ? WHERE id = 1";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "users"); // 无法将表名作为参数传入

2. 动态的 SQL 关键词或子句

像 `SELECT`、`FROM`、`WHERE`、`GROUP BY`、`ORDER BY`等 SQL 关键词或整个子句都无法作为参数传入

例如，如果你想根据用户输入动态改变排序规则，你不能这样做：

    // 错误的预编译用法
    String sql = "SELECT * FROM products ORDER BY ?";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "price DESC"); // 无法将排序规则作为参数传入

3. 动态的 `IN` 子句中的值列表

`IN` 子句中的值列表长度是可变的，预编译的占位符数量是固定的。因此，你不能直接将整个列表作为参数传入

例如，如果你想查询多个ID的用户，不能这样做：

    // 错误的预编译用法
    String sql = "SELECT * FROM users WHERE id IN (?)";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "101, 102, 103"); // 字符串“101, 102, 103”会被当作一个值

正确的做法：动态生成占位符

对于这种情况，你需要在代码中根据用户输入的列表动态生成相应数量的占位符

    // 正确的做法
    List<Integer> userIds = getUserIdsFromInput(); // 假设用户输入：101, 102, 103
    StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM users WHERE id IN (");
    for (int i = 0; i < userIds.size(); i++) {
        sqlBuilder.append("?");
        if (i < userIds.size() - 1) {
            sqlBuilder.append(", ");
        }
    }
    sqlBuilder.append(")");

    String sql = sqlBuilder.toString(); // 生成的SQL：SELECT * FROM users WHERE id IN (?, ?, ?)
    PreparedStatement pstmt = conn.prepareStatement(sql);
    for (int i = 0; i < userIds.size(); i++) {
        pstmt.setInt(i + 1, userIds.get(i));
    }
    ResultSet rs = pstmt.executeQuery();