PreparedStatementのSQLを取得するにはどうすればよいですか?


160

次のメソッドシグネチャを持つ一般的なJavaメソッドがあります。

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

これは、接続を開き、PreparedStatementsqlステートメントとqueryParams可変長配列のパラメーターを使用してを構築し、それを実行し、ResultSet(にCachedRowSetImpl)をキャッシュし、接続を閉じ、キャッシュされた結果セットを返します。

エラーをログに記録するメソッドに例外処理があります。デバッグに非常に役立つので、SQLステートメントをログの一部として記録します。私の問題は、String変数をsqlログに記録すると、テンプレートステートメントが実際の値ではなく?でログに記録されることです。実行された(または実行しようとした)実際のステートメントをログに記録したい。

だから...によって実行される実際のSQLステートメントを取得する方法はありますPreparedStatementか?(なければ、それを自分自身を構築する。私はアクセスする方法を見つけることができない場合PreparedStatement's、私はおそらく私の中でそれを自分自身を構築終わるだろう、SQLをcatchES。)


1
単純なJDBCコードを作成している場合は、Apache commons-dbutils commons.apache.org/dbutilsを参照することを強くお勧めします。JDBCコードを大幅に簡素化します。
Ken Liu

回答:


173

準備済みステートメントを使用すると、「SQLクエリ」はありません。

  • プレースホルダーを含むステートメントがあります
    • DBサーバーに送信されます
    • そこで準備した
    • これは、SQLステートメントが「分析」され、解析され、それを表すいくつかのデータ構造がメモリに準備されることを意味します
  • そして、あなたはバインドされた変数を持っています
    • サーバーに送信されます
    • そして、準備されたステートメントが実行されます-それらのデータに取り組んでいます

しかし、実際の実際のSQLクエリの再構築はありません-Java側でもデータベース側でもありません。

したがって、準備されたステートメントのSQLを取得する方法はありません-そのようなSQLがないためです。


デバッグ目的の場合、解決策は次のいずれかです。

  • プレースホルダーとデータのリストを含むステートメントのコードを出力します
  • または、いくつかのSQLクエリを「手動で」「構築」します。

22
これは機能的には真実ですが、ユーティリティコードが同等の準備されていないステートメントを再構築するのを妨げるものは何もありません。たとえば、log4jdbcの場合:「ログ出力では、準備済みステートメントの場合、バインド引数がSQL出力に自動的に挿入されます。これにより、多くの場合、読みやすさとデバッグが大幅に改善されます。」ステートメントが実際にDBサーバーによって実行されている方法ではないことがわかっている限り、デバッグに非常に役立ちます。
恒星

6
これは実装にも依存します。MySQL-少なくとも数年前に使用していたバージョン-では、JDBCドライバーはテンプレートとバインド変数から従来のSQLクエリを実際に構築しました。MySQLのバージョンはプリペアドステートメントをネイティブにサポートしていなかったので、JDBCドライバー内にそれらを実装しました。
ジェイ

@sidereal:「クエリを手動で作成するという意味です。しかし、あなたは私よりも良いと言った;;; @Jay:私たちは同じ種類のメカニズムをPHPに導入しています(サポートされている場合は実際に準備されたステートメント、それらをサポートしていないデータベースドライバーの疑似準備されたステートメント)
Pascal MARTIN

6
java.sql.PreparedStatementを使用している場合、prepareStatementの単純な.toString()には生成されたSQLが含まれます。これは1.8.0_60で確認済み
Pr0n

5
@Preston Oracle DBの場合、PreparedStatement#toString()はSQLを表示しません。したがって、DB JDBCドライバーに依存していると思います。
Mike Argyriou 2016

57

JDBC APIコントラクトではどこにも定義されていませんが、運が良ければ、問題のJDBCドライバーはを呼び出すだけで完全なSQLを返す場合がありますPreparedStatement#toString()。すなわち

System.out.println(preparedStatement);

少なくともMySQL 5.xおよびPostgreSQL 8.x JDBCドライバーがサポートしています。ただし、他のほとんどのJDBCドライバーはこれをサポートしていません。そのようなものがある場合、最善の策はLog4jdbcまたはP6Spyを使用することです

または、、ConnectionSQL文字列、およびステートメントの値を取り、SQL文字列と値をPreparedStatement記録した後にを返す汎用関数を作成することもできます。キックオフの例:

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をラップ(装飾)し、すべてのメソッドをオーバーライドするカスタムを実装することです。これにより、実数のメソッドを呼び出し、すべてのメソッドの値を収集し、「実際の」SQL文字列を遅延して構築します。方法は、(かなりの仕事が、ほとんどのIDEのは、Eclipseはないデコレータ方法のためautogeneratorsを提供)と呼ばれています。最後に、代わりにそれを使用してください。これは、基本的にP6Spyとコンソートがすでに内部で行っていることでもあります。 PreparedStatement PreparedStatementsetXXX()executeXXX()


これは、私が使用しているメソッド(prepareStatementメソッド)に似ています。私の質問は、それを行う方法ではありません-私の質問は、SQLステートメントを記録する方法です。私はできることを知っていますlogger.debug(sql + " " + Arrays.asList(values))-既に統合されているパラメーターを使用してsqlステートメントをログに記録する方法を探しています。ループして疑問符を置き換えることなく。
froadie

次に、私の回答の最後の段落に進むか、P6Spyを見てください。彼らはあなたのために「厄介な」ループと置き換え作業を行います;)
BalusC

P6Spyへのリンクが壊れています。
スティーブンP

@BalusC私はJDBCの初心者です。一つ疑問があります。そのようなジェネリック関数を書くと、PreparedStatement毎回作成されます。これPreparedStatementは、一度作成してすべての場所で再利用するのが効率的ではないのではないでしょうか。
ブーシャン2017年

これはvoltdb jdbcドライバーでも機能し、準備済みステートメントの完全なSQLクエリを取得します。
k0pernikus 2017

37

MySQLコネクターv。5.1.31でJava 8、JDBCドライバーを使用しています。

この方法を使用して、実際の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;

これは、OPが求めているとおりの賛成票すべてを持つ必要があります。
HuckIt 2016

Postgres-driverに同様の機能はありますか?
davidwessman 2017

それを手に入れるにはApache Derby
グナセカール2018年

それは機能せず、ClassCastExceptionをスローします:java.lang.ClassCastException:oracle.jdbc.driver.T4CPreparedStatementをcom.mysql.jdbc.JDBC4PreparedStatementにキャストできません
Ercan

1
@ErcanDuman、私の答えは普遍的ではなく、Java 8とMySQL JDBCドライバーのみをカバーしています。
userlond

21

クエリを実行していて、ResultSet(このシナリオでは少なくとも)を期待している場合は、次ResultSetgetStatement()ように呼び出すだけです。

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

変数executedQueryには、の作成に使用されたステートメントが含まれますResultSet

今、私はこの質問がかなり古いことに気づきましたが、これが誰かに役立つことを願っています。


6
@Elad Stern実行されたSQLステートメントを出力する代わりに、oracle.jdbc.driver.OraclePreparedStatementWrapper @ 1b9ce4bを出力します!ご案内ください!
AVA

@ AVA、toString()を使用しましたか?
Elad Stern

@EladStern toString()が使用されます!
AVA

@AVA、よくわかりませんが、jdbcドライバーに関係している可能性があります。mysql-connector-5を正常に使用しました。
Elad Stern 2015

3
rs.getStatement()はステートメントオブジェクトを返すだけなので、使用しているドライバーがSQLを返すかどうかを決定する.toString()を実装しているかどうかにかかっています
Daz

3

私はprepareStatement.toString()を使用してPreparedStatementからSQLを抽出しました。私の場合、toString()は次のようなStringを返します。

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

これで、regexを使用してクエリと値の両方を抽出し、マップに配置するメソッド(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()が私の場合と異なる場合は、正規表現を「調整」するだけです。


2

公式JavaドライバーでPostgreSQL 9.6.xを使用42.2.4

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

?既に置き換えられたSQLを表示します。これは私が探していたものです。postgresのケースをカバーするためにこの回答を追加しました。

こんなに簡単だとは思いもしませんでした。


1

PrepareStatementからSQLを印刷するための次のコードを実装しました

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.OraclePreparedStatementWrapperoracle.jdbc.driverでパブリックではありません。外部パッケージからはアクセスできません。そのクラスをどのように使用していますか?
スペクトル


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

それは、preparedStatementでも問題ありません。


これは単に、.toString()未熟なユーザーをだますための2、3の余分な行を含むa であり、すでにかなり前に回答されています。
ペレ

-1

私はOralce 11gを使用していますが、PreparedStatementから最終的なSQLを取得できませんでした。@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クエリの文字列テンプレートフォーマッターとして機能するようにしたので、簡単に説明できます。


4
この種のことは、SQLインジェクションの回避やパフォーマンスの向上など、準備されたステートメントを使用する理由全体を爆破します。
ticktock

1
私はあなたに100%同意します。大規模なバルクデータ統合のためにこのコードを最大で数回実行するようにし、log4jエンチラーダ全体に行かずにログ出力を取得するための迅速な方法が必要であることを明確にすべきでした。必要だった。これは本番コードに含めるべきではありません:-)
Diego Tercero

これはOracleドライバで動作します(ただし、パラメータは含まれません):((OraclePreparedStatementWrapper) myPreparedStatement).getOriginalSql()
latj

-4

これを行うには、低レベルでのSQLのロギングをサポートするJDBC接続またはドライバー、あるいはその両方が必要です。

log4jdbcを見てください


3
log4jdbcを見てから、何を?どんなふうに使うの?あなたはそのサイトに行き、実際にテクノロジーを使用する方法の明確な例なしで、プロジェクトについてランダムに取り乱しています。
Hooli
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.