关于java:我如何获得PreparedStatement的SQL?

How can I get the SQL of a PreparedStatement?

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

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

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

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

所以…有没有办法获得将由PreparedStatement运行的实际SQL语句?(没有自己建造。如果我找不到访问PreparedStatement'sSQL的方法,我可能最终会自己在catches)中构建它。)


使用准备好的语句,没有"SQL查询":

  • 您有一个包含占位符的语句
    • 它被发送到数据库服务器
    • 准备好了
    • 这意味着SQL语句被"分析",解析,一些表示它的数据结构是在内存中准备的。
  • 然后,你就有了绑定变量
    • 发送到服务器
    • 然后执行准备好的语句——处理这些数据

但是实际的SQL查询没有重新构建——既不在Java端,也不在数据库端。

所以,没有办法获得准备好的语句的SQL——因为没有这样的SQL。

出于调试目的,解决方案可以是:

  • 输出语句的代码,包括占位符和数据列表
  • 或者手动"构建"一些SQL查询。


JDBCAPI合同中没有定义它,但是如果幸运的话,所讨论的JDBC驱动程序可能只通过调用PreparedStatement#toString()返回完整的SQL。即。

1
System.out.println(preparedStatement);

至少mysql 5.x和postgresql 8.x JDBC驱动程序支持它。但是,大多数其他JDBC驱动程序不支持它。如果你有这样一个,那么你最好的选择是使用log4jdbc或p6spy。

或者,您也可以编写一个通用函数,该函数接受Connection、SQL字符串和语句值,并在记录SQL字符串和值之后返回PreparedStatement。启动示例:

1
2
3
4
5
6
7
8
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;
}

并用它作为

1
2
3
4
5
try {
    connection = database.getConnection();
    preparedStatement = prepareStatement(connection, SQL, values);
    resultSet = preparedStatement.executeQuery();
    // ...

另一种选择是实现一个自定义的PreparedStatement,它在构造上包装(修饰)真实的PreparedStatement,并重写所有方法,以便调用真实的PreparedStatement的方法,并收集所有setXXX()方法中的值,并在executeXXX()方法中的任何一个时惰性地构造"实际"SQL字符串。DS被调用(这是一项相当大的工作,但大多数IDE都为decorator方法提供了自动生成器,Eclipse就是这样做的)。最后用它来代替。这也基本上是P6SPY和女伴们在兜帽下已经做的。


我使用Java 8,JDBC驱动程序与MySQL连接器V5.5.31。

我可以用这个方法得到真正的SQL字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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如下:

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


如果您正在执行查询,并期望有一个ResultSet(至少在这个场景中),那么您可以这样简单地调用ResultSetgetStatement()

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

变量executedQuery将包含用于创建ResultSet的语句。

现在,我意识到这个问题已经很老了,但我希望它能帮助别人……


我已经使用PreparedStatement.ToString()从PreparedStatement中提取了我的SQL,在我的示例中,ToString()返回如下字符串:

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

现在我创建了一个方法(Java 8),它使用正则表达式提取查询和值,并将它们放入映射:

1
2
3
4
5
6
7
8
9
10
11
12
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;
  }

此方法返回具有键值对的映射:

1
2
"query" ->"INSERT INTO TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)"
"values" ->"value,  value,  value"

现在-如果希望值作为列表,只需使用:

1
2
List<String> values = Stream.of(yourExtractedParametersMap.get("values").split(","))
    .collect(Collectors.toList());

如果您的preparedStatement.toString()与我的情况不同,那么这只是一个"调整"regex的问题。


使用PostgreSQL 9.6 .x和官方Java驱动程序EDOCX1,0:

1
2
...myPreparedStatement.execute...
myPreparedStatement.toString()

将用显示SQL?已经换了,这就是我要找的。只是增加了这个答案来覆盖Postgres的案例。

我从没想过会这么简单。


非常晚:)但是您可以通过以下方式从OraclePreparedStatementWrapper获取原始SQL

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


用参数列表转换SQL PreparedStampent的代码段。它对我有用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  /**
         *
         * 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;
        }

我为从PrepareStatement打印SQL实现了以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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[" + "] ...");
            }

    }

如果您使用的是mysql,那么可以使用mysql的查询日志记录查询。我不知道其他供应商是否提供了这个特性,但很可能他们提供了。


我正在使用oralce 11g,无法从PreparedStatement获取最终的SQL。读完@pascal martin后,我明白了原因。

我放弃了使用PreparedStatement的想法,使用了一个符合我需要的简单文本格式化程序。下面是我的例子:

1
2
3
4
5
6
7
//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查询的字符串模板格式化程序。


功能简单:

1
2
3
4
5
6
7
8
9
10
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;
}

准备声明也可以。


要做到这一点,您需要一个JDBC连接和/或驱动程序,它支持在低级别记录SQL。

看看log4jdbc