web-dev-qa-db-ja.com

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

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

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

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

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

それで... PreparedStatementによって実行される実際のSQLステートメントを取得する方法はありますか? (Without自分でビルドします。PreparedStatement's SQLにアクセスする方法が見つからない場合は、おそらくcatchesで自分でビルドすることになります。)

139
froadie

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

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

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

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


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

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

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

System.out.println(preparedStatement);

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

または、Connection、SQL文字列、およびステートメント値を受け取り、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();
    // ...

別の選択肢は、構築時にrealPreparedStatementをラップ(装飾)し、のメソッドを呼び出すようにすべてのメソッドをオーバーライドするカスタムPreparedStatementを実装することですrealPreparedStatementおよびすべてのsetXXX()メソッドの値を収集し、executeXXX()メソッドの1つが呼び出されるたびに「実際の」SQL文字列を遅延的に構築します(作業を終了しますが、ほとんどのIDEにはデコレータメソッドのオートジェネレータがあります) 。最後に、代わりに使用します。それは基本的に、P6Spyと配偶者が既に内部で行っていることでもあります。

47
BalusC

Java 8、MySQLコネクタv。5.1.31を備えた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;
26
userlond

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

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

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

今、私はこの質問がかなり古いことを知っていますが、これが誰かを助けることを願っています。

17
Elad Stern

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]]]

これで、正規表現を使用してクエリと値の両方を抽出してマップに入れるメソッド(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());

PreparedStatement.toString()が私の場合と異なる場合、正規表現を「調整」するだけです。

2
RichardK

PostgreSQL 9.6.xを公式Javaドライバー42.2.4とともに使用:

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

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

こんなに簡単だとは思わなかったでしょう。

1

非常に遅い:)が、次の方法でOraclePreparedStatementWrapperから元のSQLを取得できます。

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();
1
user497087

MySQLを使用している場合は、 MySQLのクエリログ を使用してクエリを記録できます。他のベンダーがこの機能を提供しているかどうかはわかりませんが、可能性はあります。

0
Ionuț G. Stan

引数のリストを使用して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
Harsh Maheswari

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[" +  "] ...");
            }

    }
0
user3349710