前言
很多抓取最终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)
大功告成!!!!