我有一个使用以下方法签名的通用Java方法:
private static ResultSet runSQLResultSet(String sql, Object... queryParams)
它打开一个连接,使用sql语句和PreparedStatement
变量长度数组中的参数构建queryParams
,运行它,缓存ResultSet
(在CachedRowSetImpl
中}),关闭连接,并返回缓存的结果集。
我在记录错误的方法中有异常处理。我将sql语句记录为日志的一部分,因为它对调试很有帮助。我的问题是记录String变量sql
会使用?而不是实际值记录模板语句。我想记录已执行(或尝试执行)的实际语句。
那么......有没有办法获得PreparedStatement
运行的实际SQL语句? (没有自己构建它。如果我找不到访问PreparedStatement's
SQL的方法,我可能最终会在catch
es中自己构建它。)
答案 0 :(得分:152)
使用预准备语句,没有“SQL查询”:
但是没有重构真正的实际SQL查询 - 既不是在Java端,也不是在数据库端。
因此,没有办法获得预准备语句的SQL - 因为没有这样的SQL。
出于调试目的,解决方案要么:
答案 1 :(得分:47)
它在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();
// ...
另一种方法是实现一个自定义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