How can I get the SQL of a PreparedStatement?
我有一个通用的Java方法,有以下方法签名:
它打开连接,使用SQL语句和
我在记录错误的方法中有异常处理。我将SQL语句作为日志的一部分进行日志记录,因为它对调试非常有帮助。我的问题是,记录字符串变量
所以…有没有办法获得将由
使用准备好的语句,没有"SQL查询":
- 您有一个包含占位符的语句
- 它被发送到数据库服务器
- 准备好了
- 这意味着SQL语句被"分析",解析,一些表示它的数据结构是在内存中准备的。
- 然后,你就有了绑定变量
- 发送到服务器
- 然后执行准备好的语句——处理这些数据
但是实际的SQL查询没有重新构建——既不在Java端,也不在数据库端。
所以,没有办法获得准备好的语句的SQL——因为没有这样的SQL。
出于调试目的,解决方案可以是:
- 输出语句的代码,包括占位符和数据列表
- 或者手动"构建"一些SQL查询。
JDBCAPI合同中没有定义它,但是如果幸运的话,所讨论的JDBC驱动程序可能只通过调用
1 |
至少mysql 5.x和postgresql 8.x JDBC驱动程序支持它。但是,大多数其他JDBC驱动程序不支持它。如果你有这样一个,那么你最好的选择是使用log4jdbc或p6spy。
或者,您也可以编写一个通用函数,该函数接受
1 2 3 4 5 6 7 8 | 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; } |
并用它作为
1 2 3 4 5 | try { connection = database.getConnection(); preparedStatement = prepareStatement(connection, SQL, values); resultSet = preparedStatement.executeQuery(); // ... |
另一种选择是实现一个自定义的
我使用Java 8,JDBC驱动程序与MySQL连接器V5.5.31。
我可以用这个方法得到真正的SQL字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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如下:
1 | INSERT INTO oc_manufacturer SET manufacturer_id = 23, name = 'Google', sort_order=0; |
如果您正在执行查询,并期望有一个
变量
现在,我意识到这个问题已经很老了,但我希望它能帮助别人……
我已经使用PreparedStatement.ToString()从PreparedStatement中提取了我的SQL,在我的示例中,ToString()返回如下字符串:
1 2 3 | org.hsqldb.jdbc.JDBCPreparedStatement@7098b907[sql=[INSERT INTO TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)], parameters=[[value], [value], [value]]] |
现在我创建了一个方法(Java 8),它使用正则表达式提取查询和值,并将它们放入映射:
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
此方法返回具有键值对的映射:
1 2 | "query" ->"INSERT INTO TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)" "values" ->"value, value, value" |
现在-如果希望值作为列表,只需使用:
1 2 | List<String> values = Stream.of(yourExtractedParametersMap.get("values").split(",")) .collect(Collectors.toList()); |
如果您的preparedStatement.toString()与我的情况不同,那么这只是一个"调整"regex的问题。
使用PostgreSQL 9.6 .x和官方Java驱动程序EDOCX1,0:
1 2 | ...myPreparedStatement.execute... myPreparedStatement.toString() |
将用显示SQL?已经换了,这就是我要找的。只是增加了这个答案来覆盖Postgres的案例。
我从没想过会这么简单。
非常晚:)但是您可以通过以下方式从OraclePreparedStatementWrapper获取原始SQL
1 | ((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql(); |
用参数列表转换SQL PreparedStampent的代码段。它对我有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * * 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; } |
我为从PrepareStatement打印SQL实现了以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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[" + "] ..."); } } |
如果您使用的是mysql,那么可以使用mysql的查询日志记录查询。我不知道其他供应商是否提供了这个特性,但很可能他们提供了。
我正在使用oralce 11g,无法从PreparedStatement获取最终的SQL。读完@pascal martin后,我明白了原因。
我放弃了使用PreparedStatement的想法,使用了一个符合我需要的简单文本格式化程序。下面是我的例子:
1 2 3 4 5 6 7 | //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查询的字符串模板格式化程序。
功能简单:
1 2 3 4 5 6 7 8 9 10 |
准备声明也可以。
要做到这一点,您需要一个JDBC连接和/或驱动程序,它支持在低级别记录SQL。
看看log4jdbc