基于PreparedStatement抓取带参最终SQL(oracle,mysql,PostgreSQL等通用)

前言

很多抓取最终SQL的方法,都是带着?的。比如:


SELECT value from sys_param where name=?

我们现在想把 ? 给去掉。有什么办法呢

方法1 编写工具类

(该方法有些情况下是不适用的,比如oracle数据库,该工具类就实测不生效),oracle或者其它数据库通用的生效方法见方法2.

工具类参考资料来源:https://www.cnblogs.com/wggj/p/12762648.html

工具类代码:


import oracle.jdbc.internal.OraclePreparedStatement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.PreparedStatement;
import java.sql.SQLException;

public class SqlUtil {
    public static final Logger logger = LoggerFactory.getLogger(SqlUtil.class);

    public static String getSql(PreparedStatement ps) {
        try {
            if (ps == null || ps.getConnection() == null)
                return null;
            switch (ps.getConnection().getMetaData().getDatabaseProductName().toUpperCase()) {
                case "ORACLE":
                    OraclePreparedStatement ops = (OraclePreparedStatement) ps;
                    ops.get
                    return ops.getOriginalSql();
                case "MYSQL":
                    String temp = ps.toString();
                    return temp.substring(temp.indexOf(':') + 1);
                case "POSTGRESQL":
                    return ps.toString();
            }
        } catch (SQLException e) {
            logger.error("sql异常", e);
            return null;
        }
        return ps.toString();
    }
}

如何使用该工具类:


PreparedStatement ps = null;
int count = 0;
int ord = 1;

for (Map<String, Object> map : data) {
	// 示例SQL
    String sql = "insert into myTest (ORD,INPUT_DATE,LASTUPD_DTM,BILLRECID,YM) values(?,?,?,?,?)";

    ps = conn.prepareStatement(sql);
    ps.setInt(1, ord);
    java.util.Date utilDate = new java.util.Date();
    java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
    ps.setDate(2, sqlDate);
    ps.setDate(3, sqlDate);
    ps.setString(4, param.getDefineName());
    ps.setString(5, zheJiuParamVO.getJizqj());
//使用该工具类打印最终SQL:
    logger.error("SQL语句:" + SqlUtil.getSql(ps));

	ps.executeUpdate();
}

方法1在mysql生效,oracle实测不生效。直接看方法2

方法2:所有数据库(含Oracle)通用方法!

思路是这样的,我们其实要做的事情就是将所有的?替换为具体的参数。所以我们可以从PreparedStatement入手,写一个自定义日志记录的PreparedStatement。

步骤1:建立自定义PreparedStatement

建立一个空类,自行implements其中方法:

public class LoggingPreparedStatement implements PreparedStatement {
	//自行implements其中方法
}

步骤2:添加一些自定义方法:

读下面代码之前,先解释一下是啥意思:

  • 首先是添加一个 delegate (代理),这个代理只做一件事情,就是this.delegate = con.prepareStatement(sql);,调用原生的 con.prepareStatement(sql) ,这样不管是将来走哪个PrepareStatement 的实现类,都会进行兼容(步骤三还会再次用到这个delegate)
  • 然后就是一个打印sql语句的getSQL方法,这个方法就是咱们要使用的,将来会调用 getSQL 来得到不带 ?
    的最终SQL

public class LoggingPreparedStatement implements PreparedStatement {

	private PreparedStatement delegate;

    private String sql;

    private Map<Integer,Object> parameter = new TreeMap<Integer,Object>();

    public LoggingPreparedStatement(Connection con, String sql) throws SQLException {
        this.sql = sql;
        //代理
        this.delegate = con.prepareStatement(sql);
    }


	//实际打印SQL语句:
    public String getSQL() {
        String returnSQL = sql;
        //TreeMap returns sorted by key
        for(Object o : parameter.values()) {
            //Replace first ? with the value
            returnSQL = returnSQL.replaceFirst("\\?", o.toString());
        }
        return returnSQL;
    }


//下面是自行implements其中方法
//下面是自行implements其中方法
//下面是自行implements其中方法
//下面是自行implements其中方法
// .....
// .....
// .....
}

步骤3:利用delegate (代理) 重写原有的PreparedStatement方法:

示例:

主要做了这样的事情,parameter这个全局变量是我们getSQL()打印要用到的,利用delegate执行原生的PreparedStatement方法的同时,将变量参数塞入 parameter 中。

注意,业务代码中有多少 setXXX,这里就要重写多少个

@Override
    public void setString(int parameterIndex, String x) throws SQLException {
        parameter.put(parameterIndex, x);
        delegate.setString(parameterIndex, x);
    }
@Override
    public void setDate(int parameterIndex, Date x) throws SQLException {
        parameter.put(parameterIndex, x);
        delegate.setDate(parameterIndex, x);
    }
@Override
    public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
        parameter.put(parameterIndex, x);
        delegate.setObject(parameterIndex, x);
    }

步骤4 修改业务代码,让其适配自定义PreparedStatement并且打印日志

PreparedStatement ps = null;
int count = 0;
int ord = 1;
try {
    conn.setAutoCommit(false);
    for (Map<String, Object> map : data) {
        // 示例SQL
    	String sql = "insert into myTest (ORD,INPUT_DATE,LASTUPD_DTM,BILLRECID,YM) values(?,?,?,?,?)";
		// 重点!替换PreparedStatement
		// 重点!替换PreparedStatement
        ps = new LoggingPreparedStatement(conn, sql);
//                ps = conn.prepareStatement(sql);
        ps.setInt(1, ord);
        java.util.Date utilDate = new java.util.Date();
        java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
        ps.setDate(2, sqlDate);
        ps.setDate(3, sqlDate);
        ps.setString(4, param.getDefineName());
        ps.setString(5, zheJiuParamVO.getJizqj());
		//使用和打印:
        logger.error("最终SQL:" + ((LoggingPreparedStatement) ps).getSQL());

		ps.executeUpdate();
    }

最终效果:

我们看到,即使是Oracle,也能在log中打印不带?的最终SQL:

11:06:03.180 [http-nio-8090-exec-1] ERROR SQL:
insert into myTest(ORD,INPUT_DATE,LASTUPD_DTM,BILLRECID,YM,order_no,zc_code,jqid,zj_month,zc_name,input_user,zj_amt,sz_name,zc_type) values(85834,2023-08-25,2023-08-25,bill.AssetDepreciationBillDefine,202307,202307,TY2023000504,89a79051-c000-0021-597d-4e70c0c5cae0,1,桌面工作站,系统,156.81,教学,2010199)

大功告成!!!!

你可能感兴趣的:(mysql,Linux,sql,oracle,mysql,PreparedStmt)