Как я могу получить SQL PreparedStatement? - PullRequest
139 голосов
/ 04 марта 2010

У меня есть общий метод Java со следующей сигнатурой метода:

private static ResultSet runSQLResultSet(String sql, Object... queryParams)

Он открывает соединение, создает PreparedStatement с помощью оператора sql и параметров в массиве переменной длины queryParams, запускает его, кэширует ResultSetCachedRowSetImpl), закрывает соединение и возвращает кэшированный набор результатов.

У меня есть обработка исключений в методе, который регистрирует ошибки. Я записываю SQL-оператор как часть журнала, так как он очень полезен для отладки. Моя проблема заключается в том, что запись в строковую переменную sql регистрирует оператор шаблона с? Вместо фактических значений. Я хочу записать фактический оператор, который был выполнен (или попытался выполнить).

Итак ... Есть ли способ получить фактический оператор SQL, который будет выполняться PreparedStatement? ( Без создания его самостоятельно. Если я не смогу найти способ доступа к PreparedStatement's SQL, я, вероятно, в конечном итоге сам создам его в моих catch es.)

Ответы [ 13 ]

156 голосов
/ 04 марта 2010

При использовании подготовленных операторов «SQL-запрос» отсутствует:

  • У вас есть заявление, содержащее заполнители
    • отправлено на сервер БД
    • и готовится там
    • , что означает, что оператор SQL "анализируется", анализируется, некоторая структура данных, представляющая его, подготавливается в памяти
  • И тогда у вас есть связанные переменные
    • которые отправляются на сервер
    • и подготовленный оператор выполняется - работает с этими данными

Но реальный реальный запрос SQL не перестраивается - ни на стороне Java, ни на стороне базы данных.

Таким образом, нет способа получить подготовленный оператор SQL - так как такого SQL не существует.


В целях отладки решения могут быть следующими:

  • Вывести код выписки с заполнителями и списком данных
  • Или "построить" какой-нибудь SQL-запрос "вручную".
47 голосов
/ 04 марта 2010

Это нигде не определено в контракте API JDBC, но, если вам повезет, рассматриваемый драйвер JDBC может вернуть полный SQL, просто вызвав PreparedStatement#toString(). * Т.е. 1002 *

System.out.println(preparedStatement);

По крайней мере, драйверы JDBC MySQL 5.x и PostgreSQL 8.x поддерживают его. Однако большинство других драйверов JDBC не поддерживают его. Если у вас есть такой, то лучше всего использовать Log4jdbc или P6Spy .

Кроме того, вы также можете написать обобщенную функцию, которая принимает Connection, строку SQL и значения оператора и возвращает PreparedStatement после регистрации строки SQL и значений. Пример запуска:

public static PreparedStatement prepareStatement(Connection connection, String sql, Object... values) throws SQLException {
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    for (int i = 0; i < values.length; i++) {
        preparedStatement.setObject(i + 1, values[i]);
    }
    logger.debug(sql + " " + Arrays.asList(values));
    return preparedStatement;
}

и используйте его как

try {
    connection = database.getConnection();
    preparedStatement = prepareStatement(connection, SQL, values);
    resultSet = preparedStatement.executeQuery();
    // ...

Другой альтернативой является реализация пользовательского PreparedStatement, который оборачивает (украшает) real PreparedStatement в конструкцию и переопределяет все методы так, что он вызывает методы real PreparedStatement и собирает значения во всех методах setXXX() и лениво создает «фактическую» строку SQL всякий раз, когда вызывается один из executeXXX() методов (довольно хорошо, но большинство IDE предоставляют автогенераторы для методов декоратора, Eclipse ). Наконец, просто используйте его вместо. Это также в основном то, что P6Spy и супруги уже делают под капотами.

26 голосов
/ 07 октября 2014

Я использую Java 8, драйвер JDBC с коннектором MySQL v. 5.1.31.

Я могу получить настоящую строку SQL, используя этот метод:

// 1. make connection somehow, it's conn variable
// 2. make prepered statement template
PreparedStatement stmt = conn.prepareStatement(
    "INSERT INTO oc_manufacturer" +
    " SET" +
    " manufacturer_id = ?," +
    " name = ?," +
    " sort_order=0;"
);
// 3. fill template
stmt.setInt(1, 23);
stmt.setString(2, 'Google');
// 4. print sql string
System.out.println(((JDBC4PreparedStatement)stmt).asSql());

Так что возвращает что-то вроде этого:

INSERT INTO oc_manufacturer SET manufacturer_id = 23, name = 'Google', sort_order=0;
17 голосов
/ 07 января 2015

Если вы выполняете запрос и ожидаете ResultSet (вы, по крайней мере, в этом сценарии), тогда вы можете просто вызвать ResultSet 'getStatement() примерно так:

ResultSet rs = pstmt.executeQuery();
String executedQuery = rs.getStatement().toString();

Переменная executedQuery будет содержать инструкцию, которая использовалась для создания ResultSet.

Теперь я понимаю, что этот вопрос довольно старый, но я надеюсь, что это поможет кому-то ..

2 голосов
/ 06 октября 2017

Я извлек свой sql из PreparedStatement с помощью prepareStatement.toString (). В моем случае toString () возвращает строку следующим образом:

org.hsqldb.jdbc.JDBCPreparedStatement@7098b907[sql=[INSERT INTO 
TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)],
parameters=[[value], [value], [value]]]

Теперь я создал метод (Java 8), который использует регулярные выражения для извлечения запроса и значений и помещает их в карту:

private Map<String, String> extractSql(PreparedStatement preparedStatement) {
    Map<String, String> extractedParameters = new HashMap<>();
    Pattern pattern = Pattern.compile(".*\\[sql=\\[(.*)],\\sparameters=\\[(.*)]].*");
    Matcher matcher = pattern.matcher(preparedStatement.toString());
    while (matcher.find()) {
      extractedParameters.put("query", matcher.group(1));
      extractedParameters.put("values", Stream.of(matcher.group(2).split(","))
          .map(line -> line.replaceAll("(\\[|])", ""))
          .collect(Collectors.joining(", ")));
    }
    return extractedParameters;
  }

Этот метод возвращает карту, где у нас есть пары ключ-значение:

"query" -> "INSERT INTO TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)"
"values" -> "value,  value,  value"

Теперь - если вам нужны значения в виде списка, вы можете просто использовать:

List<String> values = Stream.of(yourExtractedParametersMap.get("values").split(","))
    .collect(Collectors.toList());

Если ваш prepareStatement.toString () отличается от того, что в моем случае, это просто вопрос "корректировки" регулярного выражения.

1 голос
/ 26 октября 2018

Использование PostgreSQL 9.6.x с официальным драйвером Java 42.2.4:

...myPreparedStatement.execute...
myPreparedStatement.toString()

Покажет SQL с? уже заменил, что я и искал. Просто добавил этот ответ, чтобы покрыть дело postgres.

Я бы никогда не подумал, что это может быть так просто.

1 голос
/ 27 августа 2018

Очень поздно :), но вы можете получить исходный SQL из OraclePreparedStatementWrapper по

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();
0 голосов
/ 04 октября 2017

Фрагмент кода для преобразования SQL PreparedStaments со списком аргументов. У меня работает

  /**
         * 
         * formatQuery Utility function which will convert SQL
         * 
         * @param sql
         * @param arguments
         * @return
         */
        public static String formatQuery(final String sql, Object... arguments) {
            if (arguments != null && arguments.length <= 0) {
                return sql;
            }
            String query = sql;
            int count = 0;
            while (query.matches("(.*)\\?(.*)")) {
                query = query.replaceFirst("\\?", "{" + count + "}");
                count++;
            }
            String formatedString = java.text.MessageFormat.format(query, arguments);
            return formatedString;
        }
0 голосов
/ 13 декабря 2016

Я реализовал следующий код для печати SQL из PrepareStatement

public void printSqlStatement(PreparedStatement preparedStatement, String sql) throws SQLException{
        String[] sqlArrya= new String[preparedStatement.getParameterMetaData().getParameterCount()];
        try {
               Pattern pattern = Pattern.compile("\\?");
               Matcher matcher = pattern.matcher(sql);
               StringBuffer sb = new StringBuffer();
               int indx = 1;  // Parameter begin with index 1
               while (matcher.find()) {
             matcher.appendReplacement(sb,String.valueOf(sqlArrya[indx]));
               }
               matcher.appendTail(sb);
              System.out.println("Executing Query [" + sb.toString() + "] with Database[" + "] ...");
               } catch (Exception ex) {
                   System.out.println("Executing Query [" + sql + "] with Database[" +  "] ...");
            }

    }
0 голосов
/ 22 июня 2016

Я использую Oralce 11g и не смог получить окончательный SQL из PreparedStatement. После прочтения @Pascal MARTIN ответа я понимаю почему.

Я просто отказался от идеи использования PreparedStatement и использовал простой форматировщик текста, который соответствовал моим потребностям. Вот мой пример:

//I jump to the point after connexion has been made ...
java.sql.Statement stmt = cnx.createStatement();
String sqlTemplate = "SELECT * FROM Users WHERE Id IN ({0})";
String sqlInParam = "21,34,3434,32"; //some random ids
String sqlFinalSql = java.text.MesssageFormat(sqlTemplate,sqlInParam);
System.out.println("SQL : " + sqlFinalSql);
rsRes = stmt.executeQuery(sqlFinalSql);

Вы выяснили, что sqlInParam можно динамически построить в цикле (for, while). Я просто упростил задачу до использования класса MessageFormat в качестве формирователя шаблонов строк для запроса SQL. *

...