Как я могу получить SQL PreparedStatement?

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

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

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

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

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

Ответ 1

Используя подготовленные операторы, нет "SQL-запроса":

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

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

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


Для целей отладки решения должны быть либо:

  • Вывести код оператора с помощью заполнителей и списка данных
  • Или "создать" SQL-запрос "вручную".

Ответ 2

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

System.out.println(preparedStatement);

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

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

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, который обертывает (украшает) реальную конструкцию PreparedStatement при построении и переопределяет все методы, чтобы он вызывал методы реального PreparedStatement и собирал значения во всех setXXX() и лениво строит "фактическую" строку SQL всякий раз, когда вызывается один из методов executeXXX() (довольно много работы, но большинство IDE предоставляет автогенераторы для методов декоратора, Eclipse делает). Наконец, просто используйте его. Это также в основном то, что P6Spy и супруги уже делают под капотами.

Ответ 3

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

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

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

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

Ответ 4

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

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

// 1. make connection somehow, it 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());

Итак, он возвращает smth следующим образом:

INSERT INTO oc_manufacturer SET manufacturer_id = 23, name = 'Google', sort_order=0;

Ответ 5

Если вы используете MySQL, вы можете регистрировать запросы, используя журнал запросов MySQL. Я не знаю, предоставляют ли другие поставщики эту функцию, но, скорее всего, они делают.

Ответ 6

Просто функция:

public static String getSQL (Statement stmt){
    String tempSQL = stmt.toString();

    //please cut everything before sql from statement
    //javadb...: 
    int i1 = tempSQL.indexOf(":")+2;
    tempSQL = tempSQL.substring(i1);

    return tempSQL;
}

Это хорошо, а также для подготовленного заявления.

Ответ 7

Я использую 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-запроса.

Ответ 8

Я применил следующий код для печати 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[" +  "] ...");
            }

    }

Ответ 9

Фрагмент кода для преобразования 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;
        }

Ответ 10

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

[email protected][sql=[INSERT INTO 
TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)],
parameters=[[value], [value], [value]]]

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

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());

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

Ответ 11

Для этого вам нужно подключение JDBC и/или драйвер, который поддерживает ведение журнала sql на низком уровне.

Взгляните на log4jdbc