Como posso obter o SQL de um PreparedStatement?

Eu tenho um método Java geral com a seguinte assinatura de método:

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

Ele abre uma conexão, cria um PreparedStatement usando a instrução sql e os parâmetros na matriz queryParams variable length, executa, armazena em cache o ResultSet (em um CachedRowSetImpl ), fecha a conexão e retorna o conjunto de resultados em cache.

Eu tenho exception handling no método que registra erros. Eu registro a instrução sql como parte do log, pois é muito útil para debugging. Meu problema é que o log da variável String sql registra a declaração do template com ’em vez de valores reais. Eu quero registrar a declaração real que foi executada (ou tentou executar).

Então … Existe alguma maneira de obter a instrução SQL real que será executada por um PreparedStatement ? ( Sem construí-lo sozinho. Se eu não consigo encontrar uma maneira de acessar SQL PreparedStatement's , eu provavelmente vou acabar construindo-me na minha catch es).

Usando instruções preparadas, não há “consulta SQL”:

  • Você tem uma declaração contendo espaços reservados
    • é enviado para o servidor de database
    • e preparado lá
    • o que significa que a instrução SQL é “analisada”, analisada, alguma estrutura de dados que a representa é preparada na memory
  • E, então, você tem variables ​​vinculadas
    • que são enviados para o servidor
    • e a declaração preparada é executada – trabalhando nesses dados

Mas não há reconstrução de uma consulta SQL real – nem no lado do Java nem no lado do database.

Portanto, não há como obter o SQL da instrução preparada – já que não existe tal SQL.

Para fins de debugging, as soluções são:

  • Acima o código da declaração, com os marcadores e a lista de dados
  • Ou para “construir” alguma consulta SQL “à mão”.

Não há lugar definido no contrato da API do JDBC, mas se você tiver sorte, o driver JDBC em questão pode retornar o SQL completo apenas chamando PreparedStatement#toString() . Ou seja

 System.out.println(preparedStatement); 

Pelo menos o MySQL 5.xe os drivers JDBC do PostgreSQL 8.x suportam isso. No entanto, a maioria dos outros drivers JDBC não o suporta. Se você tem um, então sua melhor aposta é usar Log4jdbc ou P6Spy .

Alternativamente, você também pode escrever uma function genérica que usa uma Connection , uma string SQL e os valores da instrução e retorna um PreparedStatement depois de registrar a string SQL e os valores. Exemplo de pontapé inicial:

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

e usá-lo como

 try { connection = database.getConnection(); preparedStatement = prepareStatement(connection, SQL, values); resultSet = preparedStatement.executeQuery(); // ... 

Outra alternativa é implementar um PreparedStatement personalizado que envolva (decore) o PreparedStatement real na construção e substitua todos os methods para que ele chame os methods do PreparedStatement real e colete os valores em todos os methods setXXX() e construa preguiçosamente os "valores reais". "SQL string sempre que um dos methods executeXXX() é chamado (bastante trabalho, mas a maioria dos IDE fornece autogeradores para methods de decorador, o Eclipse faz). Finalmente, basta usá-lo. Isso também é basicamente o que P6Spy e consortes já fazem sob os capôs.

Estou usando o Java 8, driver JDBC com conector MySQL v. 5.1.31.

Eu posso pegar uma string SQL real usando este método:

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

Então ele retorna assim:

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

Se você está executando a consulta e esperando um ResultSet (você está neste cenário, pelo menos), então você pode simplesmente chamar getStatement() ResultSet assim:

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

A variável executedQuery conterá a instrução que foi usada para criar o ResultSet .

Agora, eu percebo que essa pergunta é bem antiga, mas espero que isso ajude alguém ..

Se você estiver usando o MySQL, você pode registrar as consultas usando o log de consultas do MySQL . Não sei se outros fornecedores fornecem esse recurso, mas é provável que eles façam isso.

Estou usando o Oralce 11g e não consegui obter o SQL final do PreparedStatement. Depois de ler a resposta do @Pascal MARTIN, eu entendo o porquê.

Acabei de abandonar a idéia de usar o PreparedStatement e usei um formatador de texto simples que atendesse às minhas necessidades. Aqui está meu exemplo:

 //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); 

Você descobre que o sqlInParam pode ser construído dinamicamente em um loop (for while) que acabei de simplificar para chegar ao ponto de usar a class MessageFormat para servir como um formatador de modelo de string para a consulta SQL.

Eu implementei o seguinte código para imprimir SQL a partir de PrepareStatement

 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[" + "] ..."); } } 

Snippet de código para converter SQL PreparedStaments com a lista de argumentos. Funciona para mim

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

Eu extraí meu sql de PreparedStatement usando preparedStatement.toString () No meu caso toString () retorna String assim:

 org.hsqldb.jdbc.JDBCPreparedStatement@7098b907[sql=[INSERT INTO TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)], parameters=[[value], [value], [value]]] 

Agora criei um método (Java 8), que está usando o regex para extrair a consulta e os valores e colocá-los no mapa:

 private Map extractSql(PreparedStatement preparedStatement) { Map 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; } 

Este método retorna o mapa onde temos pares de valores-chave:

 "query" -> "INSERT INTO TABLE_NAME(COLUMN_NAME, COLUMN_NAME, COLUMN_NAME) VALUES(?, ?, ?)" "values" -> "value, value, value" 

Agora – se você quiser valores como lista, você pode simplesmente usar:

 List values = Stream.of(yourExtractedParametersMap.get("values").split(",")) .collect(Collectors.toList()); 

Se o seu preparedStatement.toString () é diferente do meu caso, é apenas uma questão de “ajustar” o regex.

Muito tarde 🙂 mas você pode obter o SQL original de um OraclePreparedStatementWrapper por

 ((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql(); 

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

Tudo bem para o readyStatement.

Para fazer isso, você precisa de uma conexão JDBC e / ou driver que suporte o registro em log do sql em um nível baixo.

Dê uma olhada no log4jdbc