如何获取PreparedStatement的SQL?

问题

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

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

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

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

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


#1 热门回答(144 赞)

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

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

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

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

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

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

#2 热门回答(45 赞)

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

System.out.println(preparedStatement);

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

或者,你也可以编写一个通用函数,该函数采用aConnection,SQL字符串和语句值,并在记录SQL字符串和值后返回aPreparedStatement。开球示例:

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,它包含(修饰),然后覆盖所有方法,以便调用therealPreparedStatement的方法并收集所有setXXX()方法中的值,并且只要调用其中一个executeXXX()方法,就会懒惰地构造"实际"SQL字符串(相当于工作,但大多数IDE为装饰器方法提供自动生成器,Eclipse确实如此。最后只需使用它。这也基本上是P6Spy和consorts已经在幕后做的事情。


#3 热门回答(20 赞)

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