Ошибка неверного синтаксиса SQL: "...right syntax to use near ?"

Рейтинг: 0Ответов: 3Опубликовано: 11.05.2023

Имеется запрос, заданный в таком виде:

String GET_BANK_ACCOUNTS_QUERY = "SELECT * FROM bank_account WHERE merchant_id = ?";

Соответственно, выполняется запрос так:

public static List<BankAccount> getMerchantBankAccounts(String id, Connection connection) throws NoBankAccountsFoundException {
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(GET_BANK_ACCOUNTS_QUERY);
            preparedStatement.setString(1,id);
            ResultSet set = preparedStatement.executeQuery(GET_BANK_ACCOUNTS_QUERY);
            List<BankAccount> list = new ArrayList<>();
            while (set.next()) {
                StatusCondition status = (set.getString("status").equals("ACTIVE"))?
                        StatusCondition.ACTIVE: StatusCondition.DELETED;
                list.add(new BankAccount(set.getString("id"), set.getString("merchant_id"),
                        status, set.getString("account_number"),
                        set.getTimestamp("created_at").toLocalDateTime()));
            }
            return list;
        } catch (SQLException e) {
            throw new NoBankAccountsFoundException("Таких аккаунтов не найдено, проверьте id мерчанта");
        }
    }

При выполнении команды executeQuery() выбрасывается исключение

...right syntax to use near ?

Данные в БД есть и id совпадает с столбцом, по которому и стоит условие WHERE. Подскажите, что может быть не так?

Ответы

▲ 1Принят

...right syntax to use near ?

MySQL не понимает значения ? в SQL-запросе. Это действительно недопустимый синтаксис SQL. Так что каким-то образом он не был заменен в PreparedStatement.

Вместо:

PreparedStatement preparedStatement = connection.prepareStatement(GET_BANK_ACCOUNTS_QUERY);
preparedStatement.setString(1,id);
ResultSet set = preparedStatement.executeQuery(GET_BANK_ACCOUNTS_QUERY); // Ошибка!!!

Вы подменяете один запрос другим запросом! Вам нужно вызвать метод PreparedStatement::executeQuery() без аргументов вместо Statement::executeQuery(String).

Использовать:

PreparedStatement preparedStatement = connection.prepareStatement(GET_BANK_ACCOUNTS_QUERY);
preparedStatement.setString(1,id);
ResultSet set = preparedStatement.executeQuery(); // Хорошо!!!

К тому же, в вашем коде происходит утечка ресурсов. Через некоторое время работы приложения они закончатся, и ваше приложение выйдет из строя. Чтобы исправить это, вам нужно закрывать ресурсы, когда они больше не нужны. Connection, Statement и ResultSet в блоке finally блока try, где они были получены или использовать утверждение try-with-resources для автоматического закрытия этих ресурсов. Правда, при этом надо учитывать следующее, которое можно прочитать здесь: Что делать со statment, когда выпадет исключение при его закрытии в Java?.

Подробную информацию см. в руководстве по основам JDBC.

▲ 3

Как верно указал @Akina в комментарии, для выполнения PreparedStatement, возвращающего ResultSet следует использовать перегруженный метод без параметров PreparedStatement::executeQuery.

В вашем же случае будет вызываться метод из родительского класса, принимающий сырой текст SQL-запроса Statement::executeQuery(String sql), в котором вместо параметра останется знак вопроса. Об этом случае специально указано в документации по ссылке:

ResultSet executeQuery(String sql) throws SQLException
Executes the given SQL statement, which returns a single ResultSet object.
Note: This method cannot be called on a PreparedStatement or CallableStatement.

Также следует использовать try-with-resources для корректного закрытия самого запроса и связанного с ним экземпляра ResultSet:

A ResultSet object is automatically closed when the Statement object that generated it is closed, re-executed, or used to retrieve the next result from a sequence of multiple results.

Также следует пересмотреть логику обработки SQLException и выбрасывания NoBankAccountsFoundException, которое должно возникать, если результирующий список пуст.

Исправленный код:

public static List<BankAccount> getMerchantBankAccounts(String id, Connection connection) throws NoBankAccountsFoundException {
    try (PreparedStatement preparedStatement = connection.prepareStatement(GET_BANK_ACCOUNTS_QUERY)) {
        preparedStatement.setString(1, id);
        ResultSet set = preparedStatement.executeQuery();
        List<BankAccount> list = new ArrayList<>();
        while (set.next()) {
            StatusCondition status = (set.getString("status").equals("ACTIVE")) ?
                        StatusCondition.ACTIVE: StatusCondition.DELETED;
            list.add(new BankAccount(
                set.getString("id"), 
                set.getString("merchant_id"),
                status, 
                set.getString("account_number"),
                set.getTimestamp("created_at").toLocalDateTime()
            ));
        }
        if (list.isEmpty()) {
            throw new NoBankAccountsFoundException("Таких аккаунтов не найдено, проверьте id мерчанта")
        }
        return list;
    } catch (SQLException e) {
        throw new RuntimeException("Ошибка выполнения запроса: " + GET_BANK_ACCOUNTS_QUERY + "; id=" + id, e);
    }
}
▲ 2

Необходимо вызывать метод ResultSet executeQuery() throws SQLException; из класса PreparedStatement, а Вы вызываете метод ResultSet executeQuery(String sql) throws SQLException; из класса Statement, который реализуется классом PreparedStatement

А проблема заключается в том, что вы уже создали PreparedStatement затем передаёте параметры, а потом пытаете выполнить запрос не передав параметры.