从且只从一个PreparedStatement中获取执行的sql语句(包括运行时绑定的参数值),是实际工作中经常遇到的一个问题。
网上很多文章提到用自定义的增强类或中间件(p6spy, log4jdbc)来实现,但这需要对现有代码进行修改,工作量很大,能不能有更直接的办法?
由于java.sql.PreparedStatement并没有提供相应接口,此功能是否实现及如何实现,不同数据库的JDBC是不一样的。PostgreSQL和MySQL的DBC用toString接口实现了此功能;但对于Oracle,问题比较棘手。
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 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();
}
通过拦截器,拦截statementHandler,再通过上述逻辑获取SQL即可
/**
* 解析获取最终可执行SQL
*
* @author ZP.Lin
*/
@Slf4j
@Intercepts(
@Signature(
type = StatementHandler.class,
method = "update",
args = Statement.class
)
)
public class OperationSqlReportInterceptor implements Interceptor {
/**
* 获得真正的处理对象,可能多层代理.
*/
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h"));
}
return (T) target;
}
/**
* 拦截StatementHandler.update方法后执行
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
Statement s = (Statement) invocation.getArgs()[0];
PreparedStatementLogger o = realTarget(s);
Statement stmt = ((DruidPooledPreparedStatement) o.getPreparedStatement()).getStatement();
PreparedStatement pstmt = null;
// 配置druid连接时使用filters: stat配置
if (stmt instanceof PreparedStatementProxyImpl) {
pstmt = ((PreparedStatementProxyImpl) stmt).getRawObject();
}
String sql = RealSqlStrategyUtil.getSql(pstmt);
log.error("statement 解析的SQL = {}", sql);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
工具类
/**
* 获取可执行SQL策略工具
*
* @author ZP.Lin
* @create 2022-07-26 16:09
* @modified By
**/
@Slf4j
public class RealSqlStrategyUtil {
static Map<String, Function<PreparedStatement, String>> map = new HashMap<>();
/*
仿策略模式,方便后续扩展更多数据源.
*/
static {
map.put("ORACLE", ps -> {
OraclePreparedStatement ops = (OraclePreparedStatement)ps;
return ops.toString();
});
map.put("MYSQL", ps -> {
String temp = ps.toString();
return temp.substring(temp.indexOf(':') + 1);
});
map.put("POSTGRESQL", Object::toString);
}
/**
* 获取最终可执行SQL
*
* @param ps
* @return
* @throws SQLException
*/
public static String getSql(PreparedStatement ps) throws SQLException {
if (ps == null || ps.getConnection() == null) {
log.error("PreparedStatement is null ! ps = {}", ps);
return null;
}
String dbType = ps.getConnection().getMetaData().getDatabaseProductName().toUpperCase();
return getRealSqlByPreparedStatement(dbType, ps);
}
public static String getRealSqlByPreparedStatement(String dbType, PreparedStatement ps) {
Function<PreparedStatement, String> statementStringFunction = map.get(dbType);
if (statementStringFunction == null) {
throw new RuntimeException("目前只支持 " + map.keySet() + " 数据源!");
}
return statementStringFunction.apply(ps).replaceAll("\\s+", " ");
}
}
文章参考:http://www.cnblogs.com/wggj