मैं एक तैयारी के एसक्यूएल कैसे प्राप्त कर सकता हूं?


160

मेरे पास निम्न विधि हस्ताक्षर के साथ एक सामान्य जावा विधि है:

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

यह एक कनेक्शन खोलता है, PreparedStatementsql स्टेटमेंट का उपयोग करता है और queryParamsवैरिएबल लेंथ एरे में पैरामीटर बनाता है , इसे चलाता है, ResultSet(a CachedRowSetImpl) को कैश करता है, कनेक्शन को बंद करता है, और कैश्ड रिजल्ट सेट को लौटाता है।

त्रुटियों को लॉग करने वाली विधि में मेरे पास अपवाद हैंडलिंग है। मैं लॉग के भाग के रूप में sql स्टेटमेंट लॉग करता हूं क्योंकि यह डीबगिंग के लिए बहुत उपयोगी है। मेरी समस्या यह है कि स्ट्रिंग वैरिएबल लॉगिंग के sqlसाथ वास्तविक विवरण के बजाय टेम्पलेट स्टेटमेंट लॉग होता है। मैं उस वास्तविक विवरण को लॉग करना चाहता हूं जिसे निष्पादित किया गया था (या निष्पादित करने की कोशिश की गई थी)।

तो ... क्या वास्तविक एसक्यूएल बयान को प्राप्त करने का कोई तरीका है जो एक द्वारा चलाया जाएगा PreparedStatement? ( इसे खुद बनाए बिना । अगर मुझे PreparedStatement'sएसक्यूएल तक पहुंचने का कोई रास्ता नहीं मिल रहा है, तो मैं शायद इसे खुद ही catchबनाऊंगा।


1
यदि आप सीधे JDBC कोड लिख रहे हैं, तो मैं अत्यधिक Apache commons-dbutils commons.apache.org/dbutils देखने की सलाह दूंगा । यह JDBC कोड को बहुत सरल करता है।
केन लियू

जवाबों:


173

तैयार कथनों का उपयोग करते हुए, "SQL क्वेरी" नहीं है:

  • आपके पास एक बयान है, जिसमें प्लेसहोल्डर हैं
    • इसे DB सर्वर पर भेजा जाता है
    • और वहाँ तैयार किया
    • जिसका अर्थ है कि एसक्यूएल स्टेटमेंट "विश्लेषण" है, पार्स किया गया है, इसका प्रतिनिधित्व करते हुए कुछ डेटा-संरचना मेमोरी में तैयार की जाती है
  • और, फिर, आपके पास बाध्य चर हैं
    • जो सर्वर को भेजे जाते हैं
    • और तैयार विवरण को निष्पादित किया जाता है - उन आंकड़ों पर काम करना

लेकिन वास्तविक वास्तविक एसक्यूएल क्वेरी का कोई पुनः निर्माण नहीं है - न तो जावा पक्ष पर, न ही डेटाबेस पक्ष पर।

इसलिए, तैयार स्टेटमेंट एसक्यूएल प्राप्त करने का कोई तरीका नहीं है - क्योंकि ऐसी कोई एसक्यूएल नहीं है।


डिबगिंग उद्देश्य के लिए, समाधान या तो निम्नलिखित हैं:

  • प्लेसहोल्डर्स और डेटा की सूची के साथ स्टेटमेंट के कोड को ओपुत करें
  • या हाथ से "कुछ SQL क्वेरी" बनाने के लिए।

22
हालांकि यह कार्यात्मक रूप से सच है, उपयोगिता कोड को एक समान अप्रस्तुत कथन के पुनर्निर्माण से रोकना कुछ भी नहीं है। उदाहरण के लिए, log4jdbc में: "लॉग किए गए आउटपुट में, तैयार किए गए कथनों के लिए, बाइंड तर्क स्वचालित रूप से SQL आउटपुट में डाला जाता है। यह कई मामलों के लिए पठनीयता और डीबगिंग में सुधार करता है।" डिबगिंग के लिए बहुत उपयोगी है, जब तक आप जानते हैं कि यह नहीं है कि स्टेटमेंट को वास्तव में डीबी सर्वर द्वारा कैसे निष्पादित किया जा रहा है।
नाक्षत्र

6
यह कार्यान्वयन पर भी निर्भर करता है। MySQL में - कम से कम कुछ वर्षों पहले मैं जिस संस्करण का उपयोग कर रहा था - JDBC ड्राइवर ने वास्तव में टेम्पलेट से एक पारंपरिक SQL क्वेरी का निर्माण किया और चर को बाँधा। मुझे लगता है कि MySQL के संस्करण ने तैयार किए गए कथनों का मूल रूप से समर्थन नहीं किया, इसलिए उन्होंने उन्हें JDBC ड्राइवर के भीतर लागू किया।
Jay

@ साइडरल: यही मेरा मतलब है "हाथ से क्वेरी बनाएं " ; लेकिन आपने इसे मुझसे बेहतर कहा ;;; @ जय: हमारे पास PHP में एक ही तरह का मूकवाद है (समर्थित होने पर वास्तविक तैयार बयान; डेटाबेस ड्राइवरों के लिए छद्म-तैयार बयान जो उनका समर्थन नहीं करते हैं)
पास्कल मार्टिन

6
यदि आप java.sql.PreparedStatement का उपयोग कर रहे हैं, तो तैयार किए गए स्टैटेमेंट पर एक साधारण .toString () जेनरेट किया गया SQL शामिल होगा जिसे मैंने इसे 1.8.0_60 में सत्यापित किया है
Pr0n

5
ओरेकल डीबी के लिए @Preston के लिए तैयार किया गया #String () एसक्यूएल नहीं दिखाता है। इसलिए मुझे लगता है कि यह डीबी JDBC ड्राइवर से निर्भर करता है।
माइक Argyriou

57

यह JDBC API कॉन्ट्रैक्ट में कहीं भी निश्चित नहीं है, लेकिन अगर आप भाग्यशाली हैं, तो JDBC ड्राइवर विचाराधीन पूरी SQL को केवल कॉल करके वापस कर सकता है PreparedStatement#toString()। अर्थात

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जो निर्माण पर असली PreparedStatement को लपेटता है (सजाता है) और सभी तरीकों को ओवरराइड करता है ताकि यह वास्तविक के तरीकों को PreparedStatementबुलाए और सभी मूल्यों को इकट्ठा करेsetXXX() तरीकों को बुलाए और तरीकों और जब भी कोई एक हो तो "वास्तविक" एसक्यूएल स्ट्रिंग का निर्माण करे। executeXXX()विधियों (काफी काम है, लेकिन सबसे आईडीई के, ग्रहण करता है डेकोरेटर तरीकों के लिए autogenerators प्रदान करता है) कहा जाता है। अंत में बस इसके बजाय इसका उपयोग करें। यह भी मूल रूप से क्या है P6Spy और संरक्षण पहले से ही डाकू के तहत करते हैं।


यह उस विधि के समान है जिसका मैं उपयोग कर रहा हूं (आपकी तैयारी विधि)। मेरा सवाल यह नहीं है कि यह कैसे करना है - मेरा सवाल यह है कि sql स्टेटमेंट कैसे लॉग किया जाए। मुझे पता है कि मैं कर सकता हूं logger.debug(sql + " " + Arrays.asList(values))- मैं पहले से ही इसमें एकीकृत मापदंडों के साथ एसक्यूएल स्टेटमेंट को लॉग इन करने का तरीका ढूंढ रहा हूं। स्वयं को लूप किए बिना और प्रश्नवाचक चिन्ह को बदलने के बिना।
फ्रैडी

फिर मेरे उत्तर के अंतिम पैराग्राफ पर जाएं या P6Spy को देखें। वे "
नॉटी

P6Spy का लिंक अब टूट गया है।
स्टीफन पी

@ बाल्सक मैं जेडीबीसी के लिए नौसिखिया हूं। मुझे एक संदेह है। यदि आप जेनेरिक फंक्शन को लिखते हैं, तो यह PreparedStatementहर बार बन जाएगा। नहीं होगा कि इतने कुशल तरीके coz पूरे बिंदु के PreparedStatementलिए उन्हें एक बार बनाने के लिए और उन्हें हर जगह फिर से उपयोग नहीं है?
भूषण

यह तैयार कथन के लिए पूर्ण sql क्वेरी प्राप्त करने के लिए voltdb jdbc ड्राइवर पर भी काम करता है।
k0pernikus

37

मैं MySQL कनेक्टर v। 5.1.31 के साथ Java 8, JDBC ड्राइवर का उपयोग कर रहा हूं।

मुझे इस पद्धति का उपयोग करके वास्तविक एसक्यूएल स्ट्रिंग मिल सकता है:

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

यह सभी अपवित्र होना चाहिए क्योंकि यह वही है जो ओपी देख रहा है।
HuckIt

क्या Postgres-Driver के लिए एक समान कार्य है?
द्विवेदीमान

इसे कैसे प्राप्त करें Apache Derby?
गुनसेकर

यह काम नहीं करता और फेंकता ClassCastException : java.lang.ClassCastException: oracle.jdbc.driver.T4CPreparedStatement com.mysql.jdbc.JDBC4PreparedStatement में ढाला नहीं जा सकता है
Ercan

1
@ErcanDuman, मेरा उत्तर सार्वभौमिक नहीं है, यह केवल जावा 8 और MySQL JDBC ड्राइवर को कवर करता है।
userlond

21

आप क्वेरी को क्रियान्वित करने और आशा रखते हैं तो एक ResultSet(आप कम से कम, इस परिदृश्य में हैं) तो आप बस फोन कर सकते हैं ResultSet's getStatement()इतना की तरह:

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

वैरिएबल executedQueryमें वह स्टेटमेंट होगा जिसे बनाने के लिए इस्तेमाल किया गया थाResultSet

अब, मुझे एहसास हुआ कि यह सवाल काफी पुराना है, लेकिन मुझे उम्मीद है कि इससे किसी को मदद मिलेगी।


6
@ एडल्ट स्टर्न यह प्रिंट करता है, oracle.jdbc.driver.OraclePreparedStatementWrapper@1b9ce4b के बजाय निष्पादित sql स्टेटमेंट को प्रिंट करता है! कृपया हमारा मार्गदर्शन करें!
AVA

@AVA, क्या आपने स्टर्लिंग () का उपयोग किया था?
इलाद स्टर्न

@EladSternString () का उपयोग किया जाता है!
AVA

@AVA, मुझे यकीन नहीं है, लेकिन यह आपके jdbc ड्राइवर के साथ हो सकता है। मैंने mysql-कनेक्टर -5 का सफलतापूर्वक उपयोग किया है।
इलाद स्टर्न

3
rs.getStatement () सिर्फ स्टेटमेंट ऑब्जेक्ट लौटाता है, इसलिए यह नीचे है कि क्या ड्राइवर आप जिन उपकरणों का उपयोग कर रहे हैं .toString () निर्धारित करता है कि यदि आप SQL
Daz

3

मैंने तैयार किए गएStatement.toString () का उपयोग करके अपने सीडल को तैयार किया है।

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

अब मैंने एक विधि (Java 8) बनाई है, जो regex का उपयोग क्वेरी और मान दोनों को निकालने और उन्हें मानचित्र में डालने के लिए कर रहा है:

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

यदि आपका तैयार किया गया स्टेटमेंट .toString () मेरे मामले से अलग है तो यह केवल "समायोजन" रीगेक्स की बात है।


2

आधिकारिक जावा ड्राइवर के साथ PostgreSQL 9.6.x का उपयोग करना 42.2.4:

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

?पहले से प्रतिस्थापित के साथ एसक्यूएल दिखाएगा , जो कि मैं देख रहा था। पोस्टग्रेज केस को कवर करने के लिए बस इस जवाब को जोड़ा।

मैंने कभी नहीं सोचा होगा कि यह इतना सरल हो सकता है।


1

मैंने एसक्यूएल से तैयारी के लिए मुद्रण के लिए निम्नलिखित कोड लागू किया

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

    }

1

एसक्यूएल रेडीमेडमेंट्स को तर्कों की सूची में बदलने के लिए कोड स्निपेट। इससे मेरा काम बनता है

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

1

बहुत देर से :) लेकिन आप मूल SQL को OraclePreparedStatementWrapper से प्राप्त कर सकते हैं

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();

1
जब मैं रैपर का उपयोग करने की कोशिश करता हूं तो यह कहता है: oracle.jdbc.driver.OraclePreparedStatementWrapperoracle.jdbc.driver में सार्वजनिक नहीं है। बाहर के पैकेज से पहुँचा नहीं जा सकता। आप उस कक्षा का उपयोग कैसे कर रहे हैं?
स्पेक्ट्रम

0

यदि आप MySQL का उपयोग कर रहे हैं, तो आप MySQL के क्वेरी लॉग का उपयोग करके प्रश्नों को लॉग कर सकते हैं । मुझे नहीं पता कि अन्य विक्रेता इस सुविधा को प्रदान करते हैं, लेकिन संभावना है कि वे करते हैं।


-1

बस कार्य:

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

यह तैयार स्टैटमेंट के लिए ठीक है।


यह बस .toString()कुछ अतिरिक्त लाइनों के साथ अनुभवहीन उपयोगकर्ताओं को मूर्ख बनाने के लिए है, और पहले से ही जवाब दिया गया था।
पेरे

-1

मैं ओरलस 11 जी का उपयोग कर रहा हूं और फाइनल एसक्यूएल को तैयार करने के लिए प्रबंधन नहीं कर सका। @ पास्कल मार्टिन जवाब पढ़ने के बाद मुझे समझ में आया कि क्यों।

मैंने अभी-अभी रेडीस्टेडमेंट का उपयोग करने के विचार को छोड़ दिया और एक साधारण टेक्स्ट फॉर्मेटर का उपयोग किया जिसने मेरी आवश्यकताओं को पूरा किया। यहाँ मेरा उदाहरण है:

//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 को गतिशील रूप से (के लिए, जबकि) लूप में बनाया जा सकता है, मैंने अभी-अभी इसे सरल बनाया है ताकि SQL क्वेरी के लिए एक स्ट्रिंग टेम्पलेट फॉर्मेटर के रूप में सेवा करने के लिए MessageFormat वर्ग का उपयोग किया जा सके।


4
इस तरह के तैयार किए गए बयानों का उपयोग करने के लिए पूरे कारण को उड़ा देता है, जैसे कि sql इंजेक्शन से बचने और बेहतर प्रदर्शन।
टिकटॉक

1
मैं आपसे 100% सहमत हूँ। मुझे यह स्पष्ट करना चाहिए कि मैंने इस कोड को एक बड़े पैमाने पर थोक डेटा एकीकरण के लिए अधिक से अधिक बार निष्पादित किया है और पूरी तरह से log4j enchilada में जाने के बिना कुछ लॉग आउटपुट के लिए एक त्वरित तरीके की आवश्यकता है जो कि किस चीज के लिए ओवरसिल हो गया होगा? मुझे चाहिए था। यह उत्पादन कोड में नहीं जाना चाहिए :-)
डिएगो टेरसेरो

यह मेरे लिए ओरेकल ड्राइवर के साथ काम करता है (लेकिन इसमें पैरामीटर शामिल नहीं हैं):((OraclePreparedStatementWrapper) myPreparedStatement).getOriginalSql()
latj

-4

ऐसा करने के लिए आपको एक JDBC कनेक्शन और / या ड्राइवर की आवश्यकता होती है जो निम्न स्तर पर sql लॉगिंग का समर्थन करता है।

Log4jdbc पर एक नज़र डालें


3
Log4jdbc पर एक नज़र डालें और फिर क्या? आप इसका इस्तेमाल कैसे करते हैं? आप उस साइट पर जाते हैं और परियोजना के बारे में यादृच्छिक रूप से बिना किसी स्पष्ट उदाहरण के देखते हैं कि वास्तव में तकनीक का उपयोग कैसे किया जाता है।
होली
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.