PreparedStatement의 SQL을 어떻게 얻을 수 있습니까?


160

다음과 같은 메소드 서명이있는 일반적인 Java 메소드가 있습니다.

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

연결을 열고 PreparedStatementsql 문을 사용하여 변수를 작성하고 queryParams변수 길이 배열 의 매개 변수를 실행하고 실행하고 ResultSet(a CachedRowSetImpl)를 캐시 하고 연결을 닫은 다음 캐시 된 결과 세트를 리턴합니다.

오류를 기록하는 방법에 예외 처리가 있습니다. 디버깅에 매우 도움이되므로 SQL 문을 로그의 일부로 기록합니다. 내 문제는 String 변수를 sql로깅하면 템플릿 값을 실제 값 대신?로 기록한다는 것입니다. 실행 된 실제 명령문 을 기록하고 싶습니다 .

그래서 ...에 의해 실행될 실제 SQL 문을 얻는 방법이 PreparedStatement있습니까? ( 내가 직접 구축 하지 않고 . PreparedStatement'sSQL 에 액세스하는 방법을 찾을 수 없다면 아마도 내 자신 의 SQL에 직접 구축 할 수 catch있습니다.)


1
당신이 바로 JDBC 코드를 작성하는 경우, 내가보기 엔 아파치 평민 - dbutils의보고 권 해드립니다 commons.apache.org/dbutils . JDBC 코드를 크게 단순화합니다.
Ken Liu

답변:


173

준비된 명령문을 사용하면 "SQL 쿼리"가 없습니다.

  • 자리 표시자가 포함 된 명세서가 있습니다.
    • DB 서버로 전송
    • 그리고 거기에서 준비
    • 이는 SQL 문이 "분석 됨", 구문 분석 됨,이를 나타내는 일부 데이터 구조가 메모리에서 준비됨을 의미합니다.
  • 그런 다음 변수를 바인딩했습니다.
    • 서버로 전송
    • 준비된 문장이 실행됩니다.

그러나 실제 실제 SQL 쿼리의 재구성은 없습니다. Java 측이나 데이터베이스 측이 아닙니다.

따라서 준비된 명령문의 SQL을 얻을 수있는 방법이 없습니다. 그러한 SQL이 없기 때문입니다.


디버깅 목적으로 솔루션은 다음 중 하나입니다.

  • 자리 표시 자와 데이터 목록과 함께 명세서 코드를 출력합니다.
  • 또는 "손으로"일부 SQL 쿼리를 "빌드"합니다.

22
이것은 기능적으로 사실이지만 유틸리티 코드가 준비되지 않은 동등한 명령문을 재구성하는 것을 막을 수있는 것은 없습니다. 예를 들어 log4jdbc에서 : "기록 된 출력에서 ​​준비된 명령문의 경우 바인드 인수가 SQL 출력에 자동으로 삽입됩니다. 이는 많은 경우에 대한 가독성 및 디버깅을 크게 향상시킵니다." 명령문이 실제로 DB 서버에서 실행되는 방식이 아니라는 것을 알고있는 한 디버깅에 매우 유용합니다.
sidereal

6
이것은 또한 구현에 달려 있습니다. MySQL에서 (최소 몇 년 전에 사용했던 버전 인) JDBC 드라이버는 실제로 템플릿에서 기존 SQL 쿼리를 작성하고 변수를 바인딩합니다. MySQL 버전은 기본적으로 준비된 명령문을 지원하지 않았으므로 JDBC 드라이버 내에서 구현했습니다.
Jay

@sidereal : "손으로 쿼리 작성" 이라는 의미입니다 . 그러나 당신은 나보다 더 잘 말했다 ;;; @Jay : PHP에는 동일한 종류의 메카니즘이 있습니다 (지원되는 경우 실제 준비된 명령문;이를 지원하지 않는 데이터베이스 드라이버에 대한 의사 준비 명령문)
Pascal MARTIN

6
java.sql.PreparedStatement를 사용하는 경우 readyStatement의 간단한 .toString ()에는 생성 된 SQL이 포함됩니다. 1.8.0_60
Pr0n

5
@Preston Oracle DB의 경우 PreparedStatement # toString ()에 SQL이 표시되지 않습니다. 따라서 DB JDBC 드라이버에 따라 다릅니다.
Mike Argyriou

57

JDBC API 계약에는 정의되어 있지 않지만 운이 좋으면 문제가되는 JDBC 드라이버는을 호출하여 전체 SQL을 반환 할 수 있습니다 PreparedStatement#toString(). 즉

System.out.println(preparedStatement);

적어도 MySQL 5.x 및 PostgreSQL 8.x JDBC 드라이버가이를 지원합니다. 그러나 대부분의 다른 JDBC 드라이버는이를 지원하지 않습니다. 그러한 것이 있다면 가장 좋은 방법은 Log4jdbc 또는 P6Spy를 사용하는 것 입니다.

또는, ConnectionSQL 문자열 및 명령문 값을 사용하고 PreparedStatementSQL 문자열 및 값을 로깅 한 후를 리턴하는 일반 함수를 작성할 수도 있습니다. 킥오프 예 :

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을 랩 (장식)하고 모든 메소드를 재정 의하여 사용자가 실제 메소드를 호출하고 모든 메소드 에서 값을 수집하고 다음 중 하나 일 때마다 "실제"SQL 문자열을 느리게 구성 하는 사용자 정의를 구현하는 것입니다. 방법 (아주 작동하지만 대부분의 IDE의는 이클립스가하는 장식 방법에 대한 autogenerators을 제공합니다)라고합니다. 마지막으로 대신 사용하십시오. 그것은 기본적으로 P6Spy와 컨소시엄이 이미 후드에서 수행하는 것입니다. PreparedStatement PreparedStatementsetXXX()executeXXX()


그것은 내가 사용하는 방법 (preparateStatement 방법)과 유사합니다. 내 질문은 그것을하는 방법이 아닙니다-내 질문은 SQL 문 을 기록 하는 방법 입니다. 나는 내가 할 수 있다는 것을 알고있다 logger.debug(sql + " " + Arrays.asList(values))-나는 이미 통합 된 매개 변수로 SQL 문을 기록하는 방법을 찾고있다. 자신을 반복하고 물음표를 교체하지 않고.
froadie

그런 다음 내 대답의 마지막 단락으로 이동하거나 P6Spy를보십시오. 그들은 당신을 위해 "불쾌한"반복 작업을 대체합니다;)
BalusC

P6Spy에 대한 링크가 끊어졌습니다.
Stephen P

@BalusC JDBC를 처음 접합니다. 한 가지 의심이 있습니다. 그런 일반 함수를 작성하면 PreparedStatement매번 생성 됩니다. 그렇게 비효율적 인 방법은 coz의 요점은 PreparedStatement한 번만 만들고 어디서나 재사용하는 것입니까?
Bhushan

또한 voltdb jdbc 드라이버에서 작동하여 준비된 명령문에 대한 전체 SQL 쿼리를 가져옵니다.
k0pernikus

37

MySQL 커넥터 v. 5.1.31과 함께 Java 8, JDBC 드라이버를 사용하고 있습니다.

이 방법을 사용하여 실제 SQL 문자열을 얻을 수 있습니다.

// 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를 반환합니다.

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

OP가 찾고있는 것과 같이 모든 upvotes가 있어야합니다.
HuckIt

Postgres 드라이버와 비슷한 기능이 있습니까?
davidwessman

그것을 얻는 방법 Apache Derby?
Gunasekar

작동하지 않으며 ClassCastException이 발생합니다 . java.lang.ClassCastException : oracle.jdbc.driver.T4CPreparedStatement를 com.mysql.jdbc.JDBC4PreparedStatement로 캐스트 할 수 없습니다.
Ercan

1
@ ErcanDuman, 내 대답은 보편적이지 않으며 Java 8 및 MySQL JDBC 드라이버 만 포함합니다.
userlond

21

쿼리를 실행하고 기대하고있는 경우 ResultSet(당신이 적어도,이 시나리오에) 당신이 간단하게 호출 할 수 있습니다 ResultSet이야 ' getStatement()과 같이 :

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

변수 executedQuery에는를 만드는 데 사용 된 명령문이 포함됩니다 ResultSet.

이제이 질문이 상당히 오래되었다는 것을 알고 있지만 이것이 누군가에게 도움이되기를 바랍니다.


6
@Elad Stern 실행 된 sql 문을 인쇄하는 대신 oracle.jdbc.driver.OraclePreparedStatementWrapper@1b9ce4b를 인쇄합니다! 안내해주세요!
AVA

@AVA, toString ()을 사용하셨습니까?
Elad Stern

@EladStern toString ()이 사용됩니다!
AVA

@ AVA, 잘 모르겠지만 jdbc 드라이버와 관련이있을 수 있습니다. mysql-connector-5를 성공적으로 사용했습니다.
Elad Stern

3
rs.getStatement ()는 명령문 오브젝트를 리턴하기 때문에 사용중인 드라이버가 .toString ()을 구현하여 SQL을 다시 가져올 지 여부를 결정합니다.
Daz

3

readyStatement.toString ()을 사용하여 PreparedStatement에서 SQL을 추출했습니다. 제 경우에는 toString ()이 다음과 같이 String을 반환합니다.

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

이제 정규식을 사용하여 쿼리와 값을 추출하고 맵에 넣는 메소드 (Java 8)를 만들었습니다.

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

readyStatement.toString ()이 제 경우와 다른 경우 정규 표현식을 "조정"하는 것입니다.


2

공식 Java 드라이버와 함께 PostgreSQL 9.6.x 사용 42.2.4:

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

SQL이 ?이미 교체 된 상태로 표시 됩니다. postgres 사례를 다루기 위해이 답변을 추가했습니다.

나는 그것이 그렇게 간단 할 수 있다고 생각하지 않았을 것입니다.


1

PrepareStatement에서 SQL을 인쇄하기 위해 다음 코드를 구현했습니다.

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

SQL PreparedStaments를 인수 목록으로 변환하는 코드 스 니펫 그것은 나를 위해 작동

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

:) 매우 늦었지만 OraclePreparedStatementWrapper에서 원본 SQL을 얻을 수 있습니다.

((OraclePreparedStatementWrapper) preparedStatement).getOriginalSql();

1
래퍼를 사용하려고하면 oracle.jdbc.driver.OraclePreparedStatementWrapperoracle.jdbc.driver에 공개되어 있지 않습니다. 외부 패키지에서 액세스 할 수 없습니다. 그 수업을 어떻게 사용하십니까?
스펙트럼


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

readyStatement에도 좋습니다.


이것은 단순히 .toString()전문가가 아닌 사용자를 속일 수있는 몇 줄이 추가 된 것으로 이미 몇 년 전에 이미 답변을 받았습니다.
Pere

-1

Oralce 11g를 사용하고 있으며 PreparedStatement에서 최종 SQL을 가져올 수 없었습니다. @Pascal MARTIN 답변을 읽은 후 이유를 이해합니다.

방금 PreparedStatement 사용이라는 아이디어를 포기하고 내 요구에 맞는 간단한 텍스트 포맷터를 사용했습니다. 내 예는 다음과 같습니다.

//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 쿼리의 문자열 템플릿 포맷터로 사용하는 것이 간단합니다.


4
이러한 종류의 SQL 작성 및 성능 향상과 같은 준비된 명령문을 사용하는 전체 이유를 불식시킵니다.
ticktock

1
나는 당신에게 100 % 동의합니다. 나는 대량의 대량 데이터 통합을 위해이 코드를 최대 두 번 실행하도록했으며, log4j enchilada 전체에 들어 가지 않고 로그 출력을 얻는 빠른 방법이 절실히 필요하다는 것을 분명히해야했습니다. 내가 필요. 이것은 프로덕션 코드에 들어가서는 안됩니다 :-)
Diego Tercero

이것은 Oracle 드라이버와 함께 작동하지만 매개 변수는 포함하지 않습니다.((OraclePreparedStatementWrapper) myPreparedStatement).getOriginalSql()
latj

-4

이를 위해서는 낮은 수준에서 SQL 로깅을 지원하는 JDBC 연결 및 / 또는 드라이버가 필요합니다.

log4jdbc 살펴보기


3
log4jdbc를 살펴본 후 무엇을 하시겠습니까? 어떻게 사용합니까? 해당 사이트로 이동하여 실제로 기술을 사용하는 방법에 대한 명확한 예없이 프로젝트에 대한 무작위 혼란이 보입니다.
Hooli
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.