如何获取PreparedStatement的SQL?

时间:2010-03-04 20:35:48

标签: java sql jdbc prepared-statement

我有一个使用以下方法签名的通用Java方法:

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

它打开一个连接,使用sql语句和PreparedStatement变量长度数组中的参数构建queryParams,运行它,缓存ResultSet(在CachedRowSetImpl中}),关闭连接,并返回缓存的结果集。

我在记录错误的方法中有异常处理。我将sql语句记录为日志的一部分,因为它对调试很有帮助。我的问题是记录String变量sql会使用?而不是实际值记录模板语句。我想记录已执行(或尝试执行)的实际语句。

那么......有没有办法获得PreparedStatement运行的实际SQL语句? (没有自己构建它。如果我找不到访问PreparedStatement's SQL的方法,我可能最终会在catch es中自己构建它。)

13 个答案:

答案 0 :(得分:152)

使用预准备语句,没有“SQL查询”:

  • 您有一个包含占位符的声明
    • 将其发送到数据库服务器
    • 并在那里准备
    • 表示SQL语句被“分析”,解析,代表它的一些数据结构在内存中准备
  • 然后,你已经绑定了变量
    • 发送到服务器
    • 并执行准备好的声明 - 处理这些数据

但是没有重构真正的实际SQL查询 - 既不是在Java端,也不是在数据库端。

因此,没有办法获得预准备语句的SQL - 因为没有这样的SQL。


出于调试目的,解决方案要么:

  • 使用占位符和数据列表输出语句的代码
  • 或“手动”“构建”一些SQL查询。

答案 1 :(得分:47)

它在JDBC API合同中没有任何定义,但如果你很幸运,有问题的JDBC驱动程序可以通过调用PreparedStatement#toString()来返回完整的SQL。即。

System.out.println(preparedStatement);

至少MySQL 5.x和PostgreSQL 8.x JDBC驱动程序支持它。但是,大多数其他JDBC驱动程序不支持它。如果您有这样的话,那么最好的选择是使用Log4jdbcP6Spy

或者,您也可以编写一个泛型函数,该函数接受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();
    // ...

另一种方法是实现一个自定义PreparedStatement,它在构造时包装(装饰) real PreparedStatement并覆盖所有方法,以便它调用<的方法em> real PreparedStatement并收集所有setXXX()方法中的值,并且只要调用其中一个executeXXX()方法,就会懒惰地构造“实际”SQL字符串(相当重要的工作) ,但大多数IDE为装饰器方法提供自动生成器,Eclipse确实如此。最后只需使用它。这也是P6Spy和他们已经在幕后做的事情。

答案 2 :(得分:26)

我正在使用带有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;

答案 3 :(得分:17)

如果您正在执行查询并期待ResultSet(至少在这种情况下),那么您只需简单地调用ResultSet的{​​{1}}:

getStatement()

变量ResultSet rs = pstmt.executeQuery(); String executedQuery = rs.getStatement().toString(); 将包含用于创建executedQuery的语句。

现在,我意识到这个问题很老了,但我希望这有助于某人......

答案 4 :(得分:2)

我使用preparedStatement.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()与我的情况不同,那只是“调整”正则表达式的问题。

答案 5 :(得分:1)

很晚了:),但是您可以从OraclePreparedStatementWrapper获取原始SQL,

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();

答案 6 :(得分:1)

PostgreSQL 9.6.x 与正式的Java驱动程序42.2.4一起使用:

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

将显示带有?的SQL吗?已经更换了,这就是我想要的。 只需添加此答案即可涵盖postgres案例。

我永远都不会想到它会如此简单。

答案 7 :(得分:0)

如果您使用的是MySQL,则可以使用MySQL's query log记录查询。我不知道其他供应商是否提供此功能,但他们很可能会这样做。

答案 8 :(得分:0)

我实现了以下用于从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[" +  "] ...");
            }

    }

答案 9 :(得分:0)

使用参数列表转换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 :(得分:-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;
}

准备好的陈述也很好。

答案 11 :(得分:-1)

我使用Oralce 11g并且无法从PreparedStatement获取最终的SQL。在阅读@Pascal MARTIN回答后,我理解为什么。

我只是放弃了使用PreparedStatement的想法,并使用了一个符合我需求的简单文本格式化程序。这是我的榜样:

7

你弄清楚sqlInParam可以在(for,while)循环中动态构建我只是简单地说到使用MessageFormat类作为SQL查询的字符串模板格式化。

答案 12 :(得分:-4)

要执行此操作,您需要一个支持在较低级别记录sql的JDBC连接和/或驱动程序。

查看log4jdbc