web-dev-qa-db-fra.com

Comment puis-je obtenir le code SQL d'un PreparedStatement?

J'ai une méthode Java générale avec la signature de méthode suivante:

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

Il ouvre une connexion, crée une PreparedStatement à l'aide de l'instruction SQL et des paramètres du tableau queryParams d'une longueur variable, l'exécute, met en cache la ResultSet (dans une CachedRowSetImpl), ferme la connexion et renvoie le jeu de résultats mis en cache.

J'ai la gestion des exceptions dans la méthode qui enregistre les erreurs. J'enregistre l'instruction SQL dans le journal car elle est très utile pour le débogage. Mon problème est que la journalisation de la variable String sql enregistre l'instruction de modèle avec? Au lieu de valeurs réelles. Je veux enregistrer l'instruction actuelle qui a été exécutée (ou tentée de l'exécuter).

Alors ... Y a-t-il un moyen d’obtenir l’instruction SQL réelle qui sera exécutée par un PreparedStatement? ( Sans le construisant moi-même. Si je ne trouve pas un moyen d'accéder au code SQL PreparedStatement's, je finirai probablement par le construire moi-même dans ma catches.)

132
froadie

À l'aide d'instructions préparées, il n'y a pas de "requête SQL":

  • Vous avez une déclaration contenant des espaces réservés
    • il est envoyé au serveur de base de données
    • et préparé là
    • ce qui signifie que l'instruction SQL est "analysée", analysée, une structure de données la représentant est préparée en mémoire
  • Et puis, vous avez des variables liées
    • qui sont envoyés au serveur
    • et l'instruction préparée est exécutée - travail sur ces données

Mais il n'y a pas de reconstruction d'une vraie requête SQL, ni du côté de Java, ni du côté de la base de données.

Il n’ya donc aucun moyen d’obtenir le code SQL de l’instruction préparée, car ce type de code n'existe pas.


Pour le débogage, les solutions sont les suivantes:

  • Sur le code de l'instruction, avec les espaces réservés et la liste des données
  • Ou pour "construire" une requête SQL "à la main".
150
Pascal MARTIN

Cela n'est défini nulle part dans le contrat d'API JDBC, mais si vous avez de la chance, le pilote JDBC en question peut renvoyer le code SQL complet en appelant simplement PreparedStatement#toString(). C'est à dire.

System.out.println(preparedStatement);

Au moins, les pilotes JDBC MySQL 5.x et PostgreSQL 8.x le prennent en charge. Cependant, la plupart des autres pilotes JDBC ne le prennent pas en charge. Si vous en avez un, votre meilleur choix consiste à utiliser Log4jdbc ou P6Spy

Sinon, vous pouvez également écrire une fonction générique qui prend une Connection, une chaîne SQL et les valeurs de l'instruction et renvoie une PreparedStatement après la journalisation de la chaîne SQL et des valeurs. Exemple de coup d'envoi:

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

et l'utiliser comme 

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

Une autre alternative consiste à implémenter une PreparedStatement personnalisée qui enveloppe (décore) le realPreparedStatement sur la construction et remplace toutes les méthodes afin qu’elle appelle les méthodes de realPreparedStatement et collecte les valeurs de toutes les méthodes setXXX() et paresseusement construit la chaîne SQL "réelle" chaque fois que l'une des méthodes executeXXX() est appelée (un travail considérable, mais la plupart des environnements de développement intégrés fournissent des générateurs automatiques pour les méthodes décoratrices, comme Eclipse le fait). Enfin, utilisez-le à la place. C'est aussi fondamentalement ce que P6Spy et ses partenaires font déjà sous le capot.

47
BalusC

J'utilise Java 8, pilote JDBC avec le connecteur MySQL v. 5.1.31.

Je peux obtenir une vraie chaîne SQL en utilisant cette méthode:

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

Donc, ça retourne comme ça:

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

Si vous exécutez la requête et attendez un ResultSet (vous êtes dans ce scénario, au moins), vous pouvez simplement appeler ResultSet's getStatement() comme suit:

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

La variable executedQuery contiendra l’instruction utilisée pour créer le ResultSet.

Maintenant, je me rends compte que cette question est assez ancienne, mais j'espère que cela aidera quelqu'un ..

18
Elad Stern

J'ai extrait mon sql de PreparedStatement en utilisant prepareStatement.toString () Dans mon cas, toString () renvoie String comme ceci:

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

Maintenant, j'ai créé une méthode (Java 8), qui utilise regex pour extraire à la fois la requête et les valeurs et les mettre dans la carte:

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

Cette méthode retourne map où nous avons des paires clé-valeur:

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

Maintenant, si vous voulez des valeurs sous forme de liste, vous pouvez simplement utiliser: 

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

Si votre readyStatement.toString () est différent de ce qu'il est dans mon cas, il vous suffit "d'ajuster" la regex.

2
RichardK

Utilisation de PostgreSQL 9.6.x avec le pilote Java officiel 42.2.4:

...myPreparedStatement.execute...
myPreparedStatement.toString()

Est-ce que montrer le SQL avec le? déjà remplacé, ce que je cherchais… .. Je viens d'ajouter cette réponse pour couvrir le cas postgres.

Je n'aurais jamais pensé que cela puisse être aussi simple.

1
Christophe Roussy

Très tard :) mais vous pouvez obtenir le code SQL d'origine d'un OraclePreparedStatementWrapper en

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();
1
user497087

Si vous utilisez MySQL, vous pouvez enregistrer les requêtes à l’aide de le journal des requêtes de MySQL . Je ne sais pas si d'autres fournisseurs proposent cette fonctionnalité, mais c'est probablement le cas.

0
Ionuț G. Stan

Extrait de code permettant de convertir SQL PreparedStaments avec la liste des arguments. Ça marche pour moi

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

J'ai implémenté le code suivant pour l'impression SQL à 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[" +  "] ...");
            }

    }
0
user3349710