PreparedStatement'ın SQL'ini nasıl edinebilirim?


160

Aşağıdaki yöntem imzası ile genel bir Java yöntemi var:

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

Bir bağlantı açar PreparedStatement, sql deyimini ve queryParamsdeğişken uzunluk dizisindeki parametreleri kullanarak oluşturur , çalıştırır, ResultSet(a CachedRowSetImpl) 'da önbelleğe alır , bağlantıyı kapatır ve önbelleğe alınan sonuç kümesini döndürür.

Hata günlükleri yönteminde özel durum işleme var. Hata ayıklama için çok yararlı olduğu için günlüğün bir parçası olarak sql deyimini günlüğe kaydederim. Benim sorunum, String değişkeninin sqlgünlüğe kaydedilmesi, şablon deyimini gerçek değerler yerine? Yürütülen (veya yürütmeye çalışılan) gerçek deyimi günlüğe kaydetmek istiyorum .

Yani ... tarafından çalıştırılacak gerçek SQL deyimi almak için herhangi bir yolu var mı PreparedStatement? ( Olmadan kendim inşa. Ben erişmek için bir yol bulamazsa PreparedStatement'sherhalde benim de kendim inşa bitireceğiz, SQL catches.)


1
Düz JDBC kodu yazıyorsanız, Apache commons-dbutils commons.apache.org/dbutils dosyasına bakmanızı şiddetle tavsiye ederim . JDBC kodunu büyük ölçüde basitleştirir.
Ken Liu

Yanıtlar:


173

Hazırlanan ifadeleri kullanarak, "SQL sorgusu" yoktur:

  • Yer tutucu içeren bir ifadeniz var
    • DB sunucusuna gönderilir
    • ve orada hazırlandı
    • SQL ifadesinin "analiz edildiği", ayrıştırıldığı, bunu temsil eden bazı veri yapılarının bellekte hazırlandığı anlamına gelir
  • Ve sonra, bağlı değişkenleriniz var
    • bunlar sunucuya gönderilir
    • ve hazırlanan beyan yürütülür - bu veriler üzerinde çalışmak

Ancak gerçek bir gerçek SQL sorgusunun yeniden oluşturulması yoktur - ne Java tarafında ne de veritabanı tarafında.

Yani, böyle bir SQL olmadığı için hazırlanan ifadenin SQL'ini almanın bir yolu yoktur.


Hata ayıklama amacıyla, çözümler ya:

  • Yer tutucular ve veri listesi ile ifadenin kodunu çıkarın
  • Veya "elle" bazı SQL sorgu "oluşturmak" için.

22
Bu işlevsel olarak doğru olsa da, yardımcı program kodunun eşdeğer bir hazırlıksız ifadeyi yeniden yapılandırmasını engelleyen hiçbir şey yoktur. Örneğin, log4jdbc içinde: "Günlüğe kaydedilen çıktıda, hazırlanmış deyimler için, bağlama bağımsız değişkenleri otomatik olarak SQL çıktısına eklenir. Bu, birçok durumda okunabilirliği ve hata ayıklamayı büyük ölçüde geliştirir." Deyimin DB sunucusu tarafından yürütülmediğinin farkında olduğunuz sürece hata ayıklama için çok kullanışlıdır.
sidereal

6
Bu aynı zamanda uygulamaya da bağlıdır. MySQL'de - en azından birkaç yıl önce kullandığım sürüm - JDBC sürücüsü aslında şablondan geleneksel bir SQL sorgusu oluşturdu ve değişkenleri bağladı. MySQL'in hazırlanan deyimleri yerel olarak desteklemediğini sanıyorum, bu yüzden bunları JDBC sürücüsü içinde uyguladılar.
Jay

@sidereal: "sorguyu elle inşa et" ile kastettiğim bu ; ama sen benden daha iyi dedin ;;; @Jay: PHP'de aynı türde bir mekanizmaya sahibiz (desteklendiğinde gerçek hazırlanmış ifadeler; desteklemeyen veritabanı sürücüleri için sözde hazırlanmış ifadeler)
Pascal MARTIN

6
Java.sql.PreparedStatement kullanıyorsanız, readyStatement üzerindeki basit bir .toString () yöntemi, oluşturulan SQL'i içerecektir. Bunu 1.8.0_60
Pr0n

5
@Preston Oracle DB için PreparedStatement # toString () SQL'i göstermez. Bu nedenle sanırım DB JDBC sürücüsüne bağlıdır.
Mike Argyriou

57

JDBC API sözleşmesinde hiçbir yerde tanımlanmamıştır, ancak şanslıysanız, söz konusu JDBC sürücüsü yalnızca arayarak tam SQL'i döndürebilir PreparedStatement#toString(). yani

System.out.println(preparedStatement);

En azından MySQL 5.x ve PostgreSQL 8.x JDBC sürücüleri bunu desteklemektedir. Ancak, çoğu JDBC sürücüsü bunu desteklemez. Böyle bir şey varsa, en iyi bahsiniz Log4jdbc veya P6Spy kullanmaktır .

Alternatif olarak, a Connection, SQL dizesi ve ifade değerlerini alan ve PreparedStatementSQL dizesini ve değerleri günlüğe kaydettikten sonra döndüren genel bir işlev de yazabilirsiniz . Başlangıç ​​örneği:

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

ve olarak kullan

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

Bir başka alternatif özel bir uygulamaktır PreparedStatementhangi sarar (süsleyen) gerçek PreparedStatement inşaat ve yöntemlerini çağırır böylece tüm yöntemleri geçersiz kılan gerçek PreparedStatement ve tüm değerleri toplayan setXXX()yöntem ve tembel "fiili" SQL dizesi oluşturur her biri executeXXX()yöntemleri (uzunca bir çalışma, ancak çoğu IDE en Eclipse yapar dekoratör yöntemleri için autogenerators sağlar) denir. Sonunda sadece kullanın. P6Spy ve konsoloslukların davlumbazların altında zaten yaptığı da budur.


Bu, kullandığım yönteme benziyor (preparStatement yönteminiz). Benim sorum nasıl yapılacağı değil - benim sorum sql deyiminin nasıl günlüğe kaydedileceği . Yapabileceğimi biliyorum logger.debug(sql + " " + Arrays.asList(values))- zaten içine entegre parametreleri ile sql deyimi günlüğe bir yol arıyorum. Kendimi döngüye sokmadan ve soru işaretlerini değiştirmeden.
froadie

Sonra cevabımın son paragrafına gidin ya da P6Spy'a bakın. Onlar sizin için "kötü" döngü ve değiştirme işi yapmak;)
BalusC

P6Spy bağlantısı kesildi.
Stephen P

@BalusC JDBC'ye yeniyim. Bir şüphem var. Eğer böyle genel bir işlev yazarsanız, o zaman PreparedStatementher zaman yaratacaktır . Bu kadar etkili olmayan bir yol, coz bütün mesele PreparedStatementonları bir kez oluşturmak ve her yerde yeniden kullanmak mı?
Bhushan

Bu aynı zamanda hazırlanmış bir deyim için tam sql sorgusu almak için voltdb jdbc sürücüsü üzerinde çalışır.
k0pernikus

37

Java 8, JDBC sürücüsünü MySQL bağlayıcısı v. 5.1.31 ile kullanıyorum.

Bu yöntemi kullanarak gerçek SQL dize alabilirsiniz:

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

Yani böyle bir şey döndürür:

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

Bu OP'nin tam olarak aradığı gibi tüm upvotes olmalıdır.
HuckIt

Postgres sürücüsü için benzer bir işlev var mı?
davidwessman

Nasıl alınır Apache Derby?
Gunasekar

Çalışmaz ve atar ClassCastException : java.lang.ClassCastException: oracle.jdbc.driver.T4CPreparedStatement, com.mysql.jdbc.JDBC4PreparedStatement
Ercan

1
@ErcanDuman, cevabım evrensel değil, sadece Java 8 ve MySQL JDBC sürücüsünü kapsıyor.
userlond

21

Sorguyu çalıştırmadan ve bekliyorsanız bir ResultSet(eğer en azından bu senaryoda vardır) o zaman sadece arayabileceğiniz ResultSets' getStatement()şöyle:

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

Değişken executedQuery, öğesini oluşturmak için kullanılan ifadeyi içerir ResultSet.

Şimdi, bu sorunun oldukça eski olduğunu anlıyorum, ama umarım bu birine yardımcı olur ..


6
@Elad Stern Bu, yazdırılan sql deyimi yazdırmak yerine oracle.jdbc.driver.OraclePreparedStatementWrapper@1b9ce4b yazdırır! Lütfen bize yol gösterin!
AVA

@AVA, toString () kullandınız mı?
Elad Stern

@EladStern toString () kullanılıyor!
AVA

@AVA, emin değilim ama jdbc sürücüsü ile ilgili olabilir. Ben mysql-connector-5'i başarıyla kullandım.
Elad Stern

3
rs.getStatement () sadece ifade nesnesini döndürür, bu yüzden kullandığınız sürücünün SQL'i geri alıp almayacağınızı belirleyen .toString ()
Daz

3

Hazırladığım sql PreparedStatement ile HazırlananStatement.toString () kullanarak çıkardım Benim durumumda toString () böyle String döndürür:

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

Şimdi hem sorgu hem de değerleri ayıklamak ve bunları haritaya koymak için regex kullanan bir yöntem (Java 8) oluşturdum:

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

Bu yöntem, anahtar / değer çiftlerine sahip olduğumuz haritayı döndürür:

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

Şimdi - değerleri liste olarak istiyorsanız sadece şunu kullanabilirsiniz:

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

ReadyStatement.toString () benim durumumdan farklıysa, bu sadece normal ifadeyi "ayarlamak" meselesidir.


2

Resmi Java sürücüsü ile PostgreSQL 9.6.x kullanma 42.2.4:

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

İle SQL gösterecek ? zaten değiştirilmiş olanı , aradığım şey bu. Bu cevabı postgres vakasını kapsayacak şekilde ekledim.

Bu kadar basit olabileceğini hiç düşünemezdim.


1

PrepareStatement'tan SQL yazdırmak için aşağıdaki kodu uyguladım

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

Kod Parçacığı SQL PreparedStaments bağımsız değişkenler listesi ile dönüştürmek için. Benim için çalışıyor

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

Çok geç :) ancak orijinal SQL'i bir OraclePreparedStatementWrapper'dan alabilirsiniz.

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();

1
oracle.jdbc.driver.OraclePreparedStatementWrapperSarıcıyı kullanmaya çalıştığımda şöyle diyor: oracle.jdbc.driver'da herkese açık değil. Dış paketten erişilemez. Bu Sınıfı nasıl kullanıyorsunuz?
spektrum


-1

Basitçe işlev:

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

Hazırlık için de iyidir.


Bu sadece .toString()uzman olmayan kullanıcıları kandırmak için birkaç ekstra çizgiye sahip ve zaten yıllar önce cevaplandı.
Pere

-1

Oralce 11g kullanıyorum ve son SQL'i PreparedStatement'tan almayı başaramadım. @Pascal MARTIN cevabını okuduktan sonra nedenini anlıyorum.

Sadece PreparedStatement kullanma fikrini bıraktım ve ihtiyaçlarımı karşılayan basit bir metin formatlayıcı kullandım. İşte benim örnek:

//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 dinamik bir (for, while) döngü içinde inşa edilebilir anladım ben sadece SQL sorgusu için bir dize şablon biçimlendirici olarak hizmet etmek için MessageFormat sınıf kullanma noktasına ulaşmak için düz yaptı.


4
Bu tür, sql enjeksiyonundan kaçınmak ve performansı artırmak gibi hazırlanmış ifadeleri kullanmanın tüm nedenini patlatır.
ticktock

1
Sana% 100 katılıyorum. Ben büyük bir toplu veri entegrasyonu için en fazla birkaç kez yürütülmesi için bu kodu yaptı açık olmalı ve umutsuzca ne için overkill olurdu bütün log4j enchilada girmeden bazı günlük çıkışı için hızlı bir yol gerekiyordu İhtiyacım vardı. Bu üretim koduna
girmemeli

Bu benim için Oracle sürücüsü ile çalışır (ancak parametreleri içermez):((OraclePreparedStatementWrapper) myPreparedStatement).getOriginalSql()
latj

-4

Bunu yapmak için bir JDBC Bağlantısına ve / veya sql'nin düşük düzeyde günlük kaydını destekleyen bir sürücüye ihtiyacınız vardır.

Log4jdbc'ye bir göz atın


3
Log4jdbc'ye bir bakın ve sonra ne olacak? Nasıl kullanıyorsun? Bu siteye gidersiniz ve teknolojinin gerçekte nasıl kullanılacağına dair net bir örnek olmadan proje hakkında rastgele bir sapma görürsünüz.
Hooli
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.