我如何获得PreparedStatement的SQL?
我有一个通用的Java方法,具有以下方法签名:
private static ResultSet runSQLResultSet(String sql, Object... queryParams)
它打开一个连接,使用sql语句和queryParams
可变长度数组中的参数构buildPreparedStatement
,运行它,cachingResultSet
(在CachedRowSetImpl
),closures连接并返回caching的结果集。
我在logging错误的方法中有exception处理。 我将sql语句logging为日志的一部分,因为它对debugging非常有帮助。 我的问题是,日志logging的stringvariablessql
日志的模板语句?而不是实际值。 我想logging执行(或试图执行)的实际语句。
所以…有什么办法来获得将由PreparedStatement
运行的实际SQL语句? (如果我自己没有构build它,如果我找不到一个访问PreparedStatement's
SQL的方法,那么我可能最终会自己去构build它。)
使用预处理语句,不存在“SQL查询”:
- 您有一个包含占位符的声明
- 它被发送到数据库服务器
- 并在那里准备
- 这意味着SQL语句被“分析”,parsing,代表它的一些数据结构被准备在内存中
- 然后,你有约束variables
- 哪些被发送到服务器
- 准备好的陈述被执行 – 处理这些数据
但是没有重构一个实际的真正的SQL查询 – 无论是在Java方面,还是在数据库方面。
所以,没有办法获得准备好的语句的SQL – 因为没有这样的SQL。
为了debugging目的,解决scheme要么是:
- 输出语句的代码,占位符和数据列表
- 或者“手动”build立一些SQL查询。
在JDBC API合同中没有定义,但如果幸运的话,那么所讨论的JDBC驱动程序就可以通过调用PreparedStatement#toString()
来返回完整的SQL。 即
System.out.println(preparedStatement);
至lessMySQL 5.x和PostgreSQL 8.x JDBC驱动程序支持它。 但是,大多数其他JDBC驱动程序不支持它。 如果你有这样一个,那么你最好的select是使用Log4jdbc或P6Spy 。
或者,您也可以编写一个通用函数,该函数接收一个Connection
,一个SQLstring和语句值,并在loggingSQLstring和值之后返回一个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()
方法中的值,并且懒惰地构造“actual “每当一个executeXXX()
方法被调用时,SQLstring(相当一部分工作,但是大多数IDE为decorator方法提供了自动生成器,Eclipse就是这样做的)。 最后只是用它来代替。 这也基本上是P6Spy和配合已经在引擎盖下做的。
如果您正在执行查询并期待ResultSet
(至less在这种情况下),那么您可以简单地调用ResultSet
的getStatement()
如下所示:
ResultSet rs = pstmt.executeQuery(); String executedQuery = rs.getStatement().toString();
executableQueryvariables将包含用于创buildResultSet
的语句。
现在,我意识到这个问题是相当古老的,但我希望这可以帮助一个人..
我正在使用Java 8,JDBC驱动程序和MySQL连接器v 5.1.31。
我可能会得到真正的SQLstring使用这种方法:
// 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;
如果您使用MySQL,则可以使用MySQL的查询日志logging查询 。 我不知道其他厂商是否提供这个function,但他们有机会。
简单的function:
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; }
准备好的语句也很好。
我正在使用Oralce 11g,无法从PreparedStatement获取最终的SQL。 在阅读@帕斯卡尔马丁答案我明白为什么。
我放弃了使用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)循环中dynamic构build,我只是简单地使用MessageFormat类作为SQL查询的string模板formater。
我实现了从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 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; }
我已经从PreparedStatement使用preparedStatement.toString()提取我的SQL在我的情况toString()返回string是这样的:
org.hsqldb.jdbc.JDBCPreparedStatement@7098b907[sql=[INSERT INTO TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)], parameters=[[value], [value], [value]]]
现在我创build了一个方法(Java 8),它使用正则expression式来提取查询和值,并将它们放到地图中:
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()是不同于我的情况,这只是一个“调整”正则expression式的问题。
要做到这一点,你需要一个JDBC连接和/或驱动程序,支持在较低的水平loginSQL。
看看log4jdbc