如何获取 PreparedStatement 的 SQL?
Posted
技术标签:
【中文标题】如何获取 PreparedStatement 的 SQL?【英文标题】:How can I get the SQL of a PreparedStatement? 【发布时间】:2010-03-04 20:35:47 【问题描述】:我有一个具有以下方法签名的通用 Java 方法:
private static ResultSet runSQLResultSet(String sql, Object... queryParams)
它打开一个连接,使用 sql 语句和queryParams
可变长度数组中的参数构建一个PreparedStatement
,运行它,缓存ResultSet
(在CachedRowSetImpl
中),关闭连接,然后返回缓存的结果集。
我在记录错误的方法中有异常处理。我将 sql 语句记录为日志的一部分,因为它对调试非常有帮助。我的问题是记录字符串变量sql
使用?而不是实际值记录模板语句。我想记录已执行(或尝试执行)的 实际 语句。
那么...有什么方法可以得到PreparedStatement
运行的实际SQL 语句? (不自己构建它。如果我找不到访问PreparedStatement's
SQL 的方法,我可能最终会在我的catch
es 中自己构建它。)
【问题讨论】:
如果您正在编写直接的 JDBC 代码,我强烈建议您查看 Apache commons-dbutils commons.apache.org/dbutils。它极大地简化了 JDBC 代码。 复制:***.com/questions/218113/… 【参考方案1】:使用准备好的语句,没有“SQL查询”:
您有一个包含占位符的语句 发送到数据库服务器 并在那里准备 这意味着 SQL 语句被“分析”、解析,一些表示它的数据结构在内存中准备好 然后,你有绑定变量 发送到服务器 并执行准备好的语句——处理这些数据但是没有重建实际的真实 SQL 查询——既不是在 Java 端,也不是在数据库端。
因此,没有办法获取准备好的语句的 SQL——因为没有这样的 SQL。
出于调试目的,解决方案是:
输出语句代码、占位符和数据列表 或者“手动”“构建”一些 SQL 查询。【讨论】:
虽然这在功能上是正确的,但没有什么能阻止实用程序代码重构等效的未准备语句。例如,在 log4jdbc 中:“在记录的输出中,对于准备好的语句,绑定参数会自动插入到 SQL 输出中。这大大提高了许多情况下的可读性和调试性。”对调试非常有用,只要您知道这不是 DB 服务器实际执行语句的方式。 这也取决于实现。在 mysql 中——至少是我几年前使用的版本——JDBC 驱动程序实际上从模板和绑定变量构建了一个传统的 SQL 查询。我猜那个版本的 MySQL 本身不支持准备好的语句,所以他们在 JDBC 驱动程序中实现了它们。 @sidereal :这就是我所说的 "手动构建查询" 的意思;但你说得比我好;;; @Jay:我们在 php 中使用了相同的机制(支持时的真实准备语句;不支持它们的数据库驱动程序的伪准备语句) 如果您使用 java.sql.PreparedStatement,preparedStatement 上的简单 .toString() 将包含我在 1.8.0_60 中验证过的生成的 SQL @Preston 对于 Oracle DB,PreparedStatement#toString() 不显示 SQL。因此我猜这取决于 DB JDBC 驱动程序。【参考方案2】: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
并覆盖所有方法,以便它调用 real PreparedStatement
并收集所有 setXXX()
方法中的值,并在调用 executeXXX()
方法之一时懒惰地构造“实际” SQL 字符串(这是一项工作,但大多数 IDE 为装饰器方法提供自动生成器, Eclipse 确实如此)。最后只是使用它。这也基本上是 P6Spy 和配偶在幕后所做的。
【讨论】:
这类似于我正在使用的方法(您的 prepareStatement 方法)。我的问题不是如何去做——我的问题是如何log sql 语句。我知道我可以做到logger.debug(sql + " " + Arrays.asList(values))
- 我正在寻找一种方法来记录带有已集成到其中的参数的 sql 语句。无需循环自己并替换问号。
然后转到我答案的最后一段或查看 P6Spy。他们为你做“讨厌的”循环和替换工作;)
与 P6Spy 的链接现已断开。
@BalusC 我是 JDBC 的新手。我有一个疑问。如果你写这样的通用函数,那么它每次都会创建PreparedStatement
。因为PreparedStatement
的全部意义在于创建一次并在任何地方重复使用它们,这不是很有效吗?
这也适用于 voltdb jdbc 驱动程序,以获取准备好的语句的完整 sql 查询。【参考方案3】:
我正在使用 Java 8,带有 MySQL 连接器 v. 5.1.31 的 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());
所以它会像这样返回:
INSERT INTO oc_manufacturer SET manufacturer_id = 23, name = 'Google', sort_order=0;
【讨论】:
这应该有所有的赞成票,因为它正是 OP 正在寻找的。span> Postgres-driver 有类似的功能吗? 如何获得Apache Derby
?
它不起作用并抛出 ClassCastException: java.lang.ClassCastException: oracle.jdbc.driver.T4CPreparedStatement 无法转换为 com.mysql.jdbc.JDBC4PreparedStatement跨度>
@ErcanDuman,我的回答并不通用,它仅涵盖 Java 8 和 MySQL JDBC 驱动程序。【参考方案4】:
如果您正在执行查询并期待 ResultSet
(至少您在这种情况下),那么您可以像这样简单地调用 ResultSet
的 getStatement()
:
ResultSet rs = pstmt.executeQuery();
String executedQuery = rs.getStatement().toString();
变量executedQuery
将包含用于创建ResultSet
的语句。
现在,我意识到这个问题已经很老了,但我希望这对某人有所帮助..
【讨论】:
@Elad Stern 它打印的是 oracle.jdbc.driver.OraclePreparedStatementWrapper@1b9ce4b 而不是打印执行的 sql 语句!请指导我们! @AVA,你用过 toString() 吗? @EladStern toString() 被使用! @AVA,我不确定,但可能与您的 jdbc 驱动程序有关。我已经成功使用了mysql-connector-5。 rs.getStatement() 只返回语句对象,因此取决于您使用的驱动程序是否实现 .toString() 来确定您是否会取回 SQL【参考方案5】:我已经使用preparedStatement.toString() 从PreparedStatement 中提取了我的sql 在我的例子中toString() 返回这样的字符串:
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() 与我的情况不同,那只是“调整”正则表达式的问题。
【讨论】:
【参考方案6】:使用 PostgreSQL 9.6.x 和官方 Java 驱动程序42.2.4
:
...myPreparedStatement.execute...
myPreparedStatement.toString()
将显示已替换 ?
的 SQL,这正是我所寻找的。
刚刚添加了这个答案来涵盖 postgres 案例。
我从没想过会这么简单。
【讨论】:
【参考方案7】:使用参数列表转换 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;
【讨论】:
【参考方案8】:很晚 :) 但您可以通过
从 OraclePreparedStatementWrapper 获取原始 SQL((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();
【讨论】:
当我尝试使用包装器时,它说:oracle.jdbc.driver.OraclePreparedStatementWrapper
在 oracle.jdbc.driver 中不公开。无法从外部包访问。您如何使用该类?【参考方案9】:
我实现了以下代码,用于从 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[" + "] ...");
【讨论】:
【参考方案10】:如果您使用的是 MySQL,您可以使用 MySQL's query log 记录查询。我不知道其他供应商是否提供此功能,但他们很有可能提供。
【讨论】:
【参考方案11】:简单函数:
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;
preparedStatement 也可以。
【讨论】:
这只是一个.toString()
加上几行来愚弄不熟练的用户,并且在很久以前就已经得到了回答。【参考方案12】:
我正在使用 Oralce 11g,但无法从 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 查询的字符串模板格式化程序。
【讨论】:
这样就搞砸了使用prepared statements的全部原因,例如避免sql注入和提高性能。 我 100% 同意你的看法。我应该明确表示,我让这段代码最多执行几次,以进行大规模的批量数据集成,并且迫切需要一种快速的方法来获得一些日志输出,而无需进入整个 log4j enchilada,这对于什么来说太过分了我需要。这不应该进入生产代码:-) 这适用于我的 Oracle 驱动程序(但不包括参数):((OraclePreparedStatementWrapper) myPreparedStatement).getOriginalSql()
@latj 它给出了没有绑定变量的原始 sql。我们需要用绑定变量值。【参考方案13】:
为此,您需要一个支持在低级别记录 sql 的 JDBC 连接和/或驱动程序。
看看log4jdbc
【讨论】:
看看log4jdbc然后呢?你如何使用它?您访问该站点并看到关于该项目的随机散漫,没有关于如何实际使用该技术的明确示例。以上是关于如何获取 PreparedStatement 的 SQL?的主要内容,如果未能解决你的问题,请参考以下文章
PreparedStatement - 获取最后一个 ResultSet
从 Derby 获取 PreparedStatement 查询