如何获得 PreparedStatement 的 SQL?

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

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

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

我在记录错误的方法中有异常处理。我将 sql 语句作为日志的一部分来记录,因为它对调试非常有帮助。我的问题是记录 String 变量 sql记录模板语句?而不是真正的价值观。我想记录执行(或试图执行)的 真的语句。

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

303059 次浏览

使用准备好的语句,不存在“ SQL 查询”:

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

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

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


为了进行调试,解决方案可以是:

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

为此,您需要一个 JDBC Connection 和/或驱动程序,它支持在低级别记录 sql。

看看 Log4jdbc

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,它在构造时包装(装饰) 真的 PreparedStatement,并覆盖所有方法,以便它调用 真的 PreparedStatement的方法,收集所有 setXXX()方法中的值,并且每当调用 executeXXX()方法中的一个时,就懒惰地构造“实际的”SQL 字符串(相当费劲,但大多数 IDE 都为装饰方法提供了自动生成器,Eclipse 就是这样做的)。最后还是用它吧。这基本上也是 P6Spy 和其配偶在引擎盖下已经在做的事情。

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

我使用的是 Java8,JDBC 驱动程序和 MySQL 连接器 v.5.1.31。

我可能会得到真正的 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());

所以它的返回值是这样的:

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

如果您正在执行查询并期待一个 ResultSet(至少在这个场景中是这样的) ,那么您可以像下面这样简单地调用 ResultSetgetStatement():

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

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

现在,我知道这个问题很老了,但是我希望这能帮助某些人. 。

功能简单:

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

准备声明也可以。

我正在使用 Oralce11g,无法从 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 查询的字符串模板格式化器。

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


}

代码片段,用参数列表转换 SQL 准备状态

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

我已经使用 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]]]

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

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

此方法返回具有键-值对的 map:

"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());

如果 preadStatement.toString ()与我的示例不同,那么只需要“调整”regex 即可。

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

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();

使用带有官方 Java 驱动程序 42.2.4PostgreSQL 9.6. x:

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

将显示已经替换了 ?的 SQL,这正是我所寻找的。 刚刚加上这个答案,以涵盖 Postgres 的情况。

我从没想过事情会这么简单。