一、問題提出
從且只從一個PreparedStatement中獲取執行的sql語句(包括運行時綁定的參數值),是實際工作中經常遇到的一個問題。
網上很多文章提到用自定義的增強類或中間件(p6spy, log4jdbc)來實現,但這需要對現有代碼進行修改,工作量很大,能不能有更直接的辦法?
由於java.sql.PreparedStatement並沒有提供相應接口,此功能是否實現及如何實現,不同數據庫的JDBC是不一樣的。PostgreSQL和MySQL的DBC用toString接口實現了此功能;但對於Oracle,問題比較棘手。
二、PostgreSQL和MySQL
PostgreSQL和MySQL可以直接使用toString接口獲取sql,且會得到綁定的參數值。兩者的區別在於:MySQL會在前面加上類名。
PreparedStatement ps = con.prepareStatement("SELECT value from sys_param where name=?"); ps.setString(1, "UNIT_CODE"); System.out.println(ps.toString());
PostgreSQL的輸出是:SELECT value from sys_param where name='UNIT_CODE',可以直接使用。
MySQL的輸出是:com.mysql.cj.jdbc.ClientPreparedStatement: SELECT value from sys_param where name='UNIT_CODE',此時,只需將“: ”前部分截斷即可。
但Oracle的輸出則是:oracle.jdbc.driver.OraclePreparedStatementWrapper@7b98f307,無法使用。
三、Oracle
經過仔細分析Oracle JDBC的類,發現oracle.jdbc.internal.OraclePreparedStatement(注意不能是oracle.jdbc.OraclePreparedStatement)提供了一個接口getOriginalSql()。用它進行嘗試:
PreparedStatement ps = con.prepareStatement("SELECT value from sys_param where name=?"); ps.setString(1, "UNIT_CODE"); if (ps instanceof OraclePreparedStatement) { OraclePreparedStatement ops = (OraclePreparedStatement)ps; System.out.println(ops.getOriginalSql()); }
輸出是:SELECT value from sys_param where name=?,只差綁定的參數值了。
那么,能不能進一步將綁定參數也解析出來?從目前掌握的信息看,Oracle還做不到。
四、通用方法
根據以上,可以構造一個從PreparedStatement獲取sql的通用方法,如下:
public String getSql(PreparedStatement ps) throws SQLException { if (ps==null || ps.getConnection()==null) return null; switch (ps.getConnection().getMetaData().getDatabaseProductName().toUpperCase()) { case "ORACLE": OraclePreparedStatement ops = (OraclePreparedStatement)ps; return ops.getOriginalSql(); case "MYSQL": String temp = ps.toString(); return temp.substring(temp.indexOf(':') + 1); case "POSTGRESQL": return ps.toString(); } return ps.toString(); }