Як я можу отримати SQL підготовленого стану?


160

У мене є загальний метод Java із таким підписом методу:

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

Він відкриває з'єднання, будує за PreparedStatementдопомогою оператора sql та параметрів у queryParamsмасиві змінної довжини, запускає його, кешує ResultSet(в а CachedRowSetImpl), закриває з'єднання та повертає кешований набір результатів.

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

Отже ... Чи є спосіб отримати власне оператор SQL, яким керуватиме a PreparedStatement? ( Не будуючи його самостійно. Якщо я не можу знайти спосіб доступу до PreparedStatement'sSQL, я, мабуть, в кінцевому підсумку будую його сам у своїх catchес.)


1
Якщо ви пишете прямий код JDBC, я дуже рекомендую переглянути Apache commons-dbutils commons.apache.org/dbutils . Це значно спрощує код JDBC.
Кен Лю

Відповіді:


173

Використовуючи підготовлені оператори, немає "SQL запиту":

  • У вас є заява, яка містить заповнювачі
    • він надсилається на сервер БД
    • і готували там
    • що означає, що оператор SQL "аналізується", аналізується, деяка структура даних, що представляє його, готується в пам'яті
  • А значить, ви маєте зв'язані змінні
    • які надсилаються на сервер
    • і підготовлена ​​заява виконується - працює над цими даними

Але немає реконструкції фактичного реального запиту SQL - ні з боку Java, ні з боку бази даних.

Отже, немає можливості отримати SQL підготовленого оператора - як такого SQL немає.


Для налагодження рішення або:

  • Випишіть код заяви, із заповненнями та списком даних
  • Або "скласти" якийсь запит SQL "від руки".

22
Хоча це функціонально вірно, ніщо не заважає утилітному коду реконструювати еквівалентну непідготовлену операцію. Наприклад, в log4jdbc: "У протоколі, що записується, для підготовлених операторів, аргументи зв'язування автоматично вставляються у вихід SQL. Це значно покращує читабельність та налагодження в багатьох випадках." Дуже корисно для налагодження, якщо ви усвідомлюєте, що оператор не виконує справді сервер БД.
сидереал

6
Це також залежить від реалізації. У MySQL - принаймні версії, яку я використовував кілька років тому - драйвер JDBC насправді побудував звичайний запит SQL із шаблону та зв’язав змінні. Я здогадуюсь, що версія MySQL не підтримувала підготовлені оперативні повідомлення, тому вони реалізували їх у драйвері JDBC.
Джей

@sidereal: ось що я мав на увазі під «побудувати запит вручну» ; але ти сказав це краще за мене ;;; @Jay: у PHP у нас є такий самий вид меканізму (реальні підготовлені заяви при підтримці; псевдопідготовлені заяви для драйверів баз даних, які їх не підтримують)
Pascal MARTIN

6
Якщо ви використовуєте java.sql.PreparedStatement простий .toString (), на ReadyStatement буде включений створений SQL, я перевірив це в 1.8.0_60
Pr0n,

5
@Preston Для бази даних Oracle PreparedStatement # toString () не показує SQL. Тому я думаю, це залежить від драйвера JDBC DB.
Майк Аргіріу

57

Це ніде не визначено в договорі API JDBC, але якщо вам пощастить, відповідний драйвер JDBC може повернути повний SQL, просто зателефонувавши PreparedStatement#toString(). Тобто

System.out.println(preparedStatement);

Принаймні, драйвери MySQL 5.x та PostgreSQL 8.x JDBC підтримують його. Однак більшість інших драйверів JDBC не підтримує його. Якщо у вас є такий, то найкраща ставка - це використання Log4jdbc або P6Spy .

Крім того, ви також можете записати загальну функцію, яка приймає a Connection, рядок SQL та значення оператора та повертає a 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 і консорці вже роблять під капотами.


Це схоже на метод, який я використовую (ваш метод pripreвлення). Моє питання полягає не в тому , щоб зробити це - моє питання полягає в тому , щоб увійти в інструкції SQL. Я знаю, що можу зробити logger.debug(sql + " " + Arrays.asList(values))- я шукаю спосіб увімкнути оператор sql з уже вбудованими в нього параметрами. Не розв'язуючи себе і не замінюючи знаки запитання.
froadie

Потім перейдіть до останнього пункту моєї відповіді або подивіться на P6Spy. Вони роблять для вас "бридке" циклічне та заміщення;)
BalusC

Посилання на P6Spy зараз порушено.
Стівен П

@BalusC Я новачок у JDBC. У мене є одне сумнів. Якщо ви запишете загальну функцію так, вона створюватиметься PreparedStatementкожного разу. Хіба це не дуже ефективний спосіб, адже цілий сенс - PreparedStatementце створити їх один раз і повторно використовувати їх всюди?
Бхушань

Це також працює над драйвером voltdb jdbc, щоб отримати повний запит sql для підготовленого оператора.
k0pernikus

37

Я використовую драйвер 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());

Отже, він повертає smth так:

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

Це повинно мати всі оновлення, оскільки саме це шукає ОП.
HuckIt

Чи є аналогічна функція для драйвера Postgres?
davidwessman

Як його отримати Apache Derby?
Гунасекар

Він не працює і кидає ClassCastException : java.lang.ClassCastException: oracle.jdbc.driver.T4CPreparedStatement не може бути передано до com.mysql.jdbc.JDBC4PreparedStatement
Еркан

1
@ErcanDuman, моя відповідь не універсальна, вона охоплює лише Java 8 та MySQL JDBC драйвер.
userlond

21

Якщо ви виконання запиту і очікували ResultSet(ви в цьому випадку, по крайней мере) , то ви можете просто зателефонувати ResultSet«S getStatement()виглядати приблизно так:

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

Змінна executedQueryбуде містити оператор, який був використаний для створення ResultSet.

Зараз я усвідомлюю, що це питання досить старе, але сподіваюся, що це комусь допоможе.


6
@Elad Stern Він друкує, oracle.jdbc.driver.OraclePreparedStatementWrapper@1b9ce4b замість того, щоб надрукувати виконане оператор sql! Будь ласка, направляйте нас!
AVA

@AVA, ти використовував toString ()?
Elad Stern

@EladStern toString () використовується!
AVA

@AVA, я не впевнений, але це може стосуватися вашого драйвера jdbc. Я успішно використовував mysql-connector-5.
Elad Stern

3
rs.getStatement () просто повертає об'єкт оператора, тому справа в тому, чи буде драйвер, який ви використовуєте, реалізує .toString (), який визначає, чи повернете ви SQL
дат

3

Я витягнув sql з PreparedStatement, використовуючи readyStatement.toString () У моєму випадку toString () повертає String так:

org.hsqldb.jdbc.JDBCPreparedStatement@7098b907[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());

Якщо ваш readyStatement.toString () відрізняється, ніж у моєму випадку, це лише питання "коригування" регулярного вираження.


2

Використання PostgreSQL 9.6.x з офіційним драйвером Java 42.2.4:

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

Покаже SQL із ?уже заміненим, саме це я шукав. Щойно додав цю відповідь, щоб висвітлити справу постгресу.

Я б ніколи не думав, що це може бути так просто.


1

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

    }

1

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

1

Дуже пізно :), але ви можете отримати оригінальний SQL від OraclePreparedStatementWrapper від

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();

1
Коли я намагаюся використовувати обгортку, вона говорить: oracle.jdbc.driver.OraclePreparedStatementWrapperне є загальнодоступною в oracle.jdbc.driver. Не можна отримати доступ із зовнішнього пакету. Як ти використовуєш цей клас?
спектр

0

Якщо ви використовуєте MySQL, ви можете ввести запити за допомогою журналу запитів MySQL . Я не знаю, чи надають цю функцію інші постачальники, але, швидше за все, вони є.


-1

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

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;
}

Це добре також для підготовленої Держави.


Це просто .toString()декілька додаткових ліній для того, щоб обдурити недосвідчених користувачів, і на них уже відповіли століття тому.
Пер

-1

Я використовую 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 може бути динамічно побудований у циклі (для, поки). Я просто спростив це, щоб дістатись до точки використання класу MessageFormat, щоб слугувати формою шаблона рядків для запиту SQL.


4
Цей вид підірває всю причину використання підготовлених операторів, таких як уникнення sql введення та покращення продуктивності.
галочка

1
Я згоден з вами на 100%. Я мав би дати зрозуміти, що я зробив цей код максимум пару разів для масової інтеграції масових даних, і відчайдушно потрібен був швидкий спосіб отримати деякий вихід журналу, не заглиблюючись у всю en4ilada log4j, який би був надмірним для чого Мені було потрібно. Це не повинно входити у виробничий код :-)
Дієго Терсеро

Це працює для мене з драйвером Oracle (але не включає параметри):((OraclePreparedStatementWrapper) myPreparedStatement).getOriginalSql()
latj

-4

Для цього вам потрібне з'єднання JDBC та / або драйвер, який підтримує реєстрацію sql на низькому рівні.

Погляньте на log4jdbc


3
Погляньте на log4jdbc і що тоді? Як ти ним користуєшся? Ви переходите на цей сайт і бачите випадкові суперечки щодо проекту, не маючи чіткого прикладу того, як реально використовувати технологію.
Хулі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.