众所周知,通过JdbcTemplate/NamedParameterJdbcTemplate执行的查询, log里面打印的SQL参数都是?占位符, 对于调试SQL非常不友好, 所以利用AOP将参数代入,然后再打印出来.
首先我们先写一个SQLLogger,用于拦截JdbcTemplate/NamedParameterJdbcTemplate的查询动作(于动作前拦截,用Before).
package com.freestyle.common.hibernate.dao.support;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import com.freestyle.utils.Util;
/*****
* SQL日志打印工具,可将jdbctemplate的参数值一起显示(依赖spring环境)
* @author dgmislrh
* 14/May/2018
*/
@Aspect
public class SqlLogger {
private static final Logger log = Logger.getLogger(SqlLogger.class);
@Before("execution(* org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.*(..))")
public void logNamedJdbcTemplate(JoinPoint jp) throws Throwable {
Object[] methodArgs = jp.getArgs();
Map<String,Object> sqlArgs = null;
// get the SQL statement
String statement = methodArgs[0].toString();
// find the SQL arguments (parameters)
for (int i = 1, n = methodArgs.length; i < n; i++) {
Object arg = methodArgs[i];
if (arg instanceof Map) {
sqlArgs = (Map<String,Object>)arg;
break;
}
}
// fill in any SQL parameter place-holders (?'s)
String completedStatement = (sqlArgs == null ? statement : fillNamedParameters(statement, sqlArgs));
// log it
log.debug(jp.getSignature().toShortString()+ "\n------FULL SQL------\n"+completedStatement+"\n---------------------");
}
private String fillNamedParameters(String statement,Map<String,Object> sqlArgs){
StringBuilder sb=new StringBuilder(statement);
for (Entry<String, Object>item:sqlArgs.entrySet()){
int lvnSeek=sb.indexOf(":"+item.getKey());
if (lvnSeek==-1)continue;
String lvsValue=null;
if (item.getValue()==null){
lvsValue="null";
}
else if (item.getValue() instanceof String){
lvsValue=String.format("'%s'", item.getValue());
}
else if (item.getValue() instanceof Boolean){
lvsValue=((Boolean) item.getValue()).booleanValue()?"true":"false";
}
else if (item.getValue() instanceof java.util.Date){
java.util.Date lvdDt=(java.util.Date) item.getValue();
if (lvdDt.getHours()==0&&lvdDt.getMinutes()==0&& lvdDt.getSeconds()==0){
lvsValue="timestamp '"+Util.datetimeToString((java.util.Date)item.getValue(),Util.c_java_datefmt)+"'";
}
else{
lvsValue="timestamp '"+Util.datetimeToString((java.util.Date)item.getValue(),Util.c_java_dttfmt)+"'";
}
}
else {
lvsValue=item.getValue().toString();
}
sb.replace(lvnSeek, lvnSeek+item.getKey().length()+1, lvsValue);
}
return sb.toString();
}
@Before("execution(* org.springframework.jdbc.core.JdbcOperations.*(..))")
//@Before("execution(* org.springframework.jdbc.core.namedparam.*(..))")
public void logJdbcTemplate(JoinPoint jp) throws Throwable {
Object[] methodArgs = jp.getArgs(),
sqlArgs = null;
// get the SQL statement
String statement = methodArgs[0].toString();
// find the SQL arguments (parameters)
for (int i = 1, n = methodArgs.length; i < n; i++) {
Object arg = methodArgs[i];
if (arg instanceof Object[]) {
sqlArgs = (Object[])arg;
break;
}
}
// fill in any SQL parameter place-holders (?'s)
String completedStatement = (sqlArgs == null ? statement : fillParameters(statement, sqlArgs));
// log it
log.debug(jp.getSignature().toShortString()+"\n------FULL SQL------\n"+completedStatement+"\n---------------------");
}
private String fillParameters(String statement, Object[] sqlArgs) {
// initialize a StringBuilder with a guesstimated final length
StringBuilder completedSqlBuilder = new StringBuilder(Math.round(statement.length() * 1.2f));
int index, // will hold the index of the next ?
prevIndex = 0; // will hold the index of the previous ? + 1
// loop through each SQL argument
for (Object arg : sqlArgs) {
index = statement.indexOf("?", prevIndex);
if (index == -1)
break; // bail out if there's a mismatch in # of args vs. ?'s
// append the chunk of SQL coming before this ?
completedSqlBuilder.append(statement.substring(prevIndex, index));
if (arg == null)
completedSqlBuilder.append("null");
else if (arg instanceof String) {
// wrap the String in quotes and escape any quotes within
completedSqlBuilder.append('\'')
.append(arg.toString().replace("'", "''"))
.append('\'');
}
else if (arg instanceof java.sql.Date) {
java.util.Date lvdDt=new java.sql.Date(((java.sql.Date)arg).getTime());
String lvsValue="timestamp '"+Util.datetimeToString(lvdDt,Util.c_java_dttfmt)+"'";
completedSqlBuilder.append(lvsValue);
}
else
completedSqlBuilder.append(arg.toString());
prevIndex = index + 1;
}
// add the rest of the SQL if any
if (prevIndex != statement.length())
completedSqlBuilder.append(statement.substring(prevIndex));
return completedSqlBuilder.toString();
}
}
用注解的方式拦截JdbcTemplate的查询动作,让其先执行logJdbcTemplate, 拦截NamedParameterJdbcTemplate的查询动作,让其先执行logNamedJdbcTemplate 。接着无非就是将?占位符依然换成object[]里面的值, 将命名点位符换成map里面的value,这些就不细讲了。
第二步,修改spring配置文件,在里面加上:
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource">
</constructor-arg>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource">
</constructor-arg>
</bean>
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="sqlLogger" class="com.freestyle.common.hibernate.dao.support.SqlLogger"></bean>
就是将namedParameterJdbcTemplate和jdbcTemplate也在spring里面占一实例,然后给下面的TransJdbcTemplate调用。
TransJdbcTemplate是我写的一个带事务管理的JdbcTemplate类,一般就它来实现原始SQL。
package com.freestyle.common.spring;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.context.annotation.DependsOn;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/****
* 带有事务管理的JdbcTemplate
* @author dgmislrh
*
*/
@DependsOn({"dsTransactionManager"})
//@Repository("transJdbcTemplate")
public class TransJdbcTemplate extends org.springframework.jdbc.core.JdbcTemplate{
@Resource(name="dsTransactionManager")
private org.springframework.jdbc.datasource.DataSourceTransactionManager dsTm;
public static interface TransCallback{
public void onExecute(JdbcTemplate pvJt,NamedParameterJdbcTemplate pvNamedJt) throws Exception;
}
public static interface NamedTransCallback{
public void onExecute(NamedParameterJdbcTemplate pvJt) throws Exception;
}
public TransJdbcTemplate(DataSource dataSource) {
super (dataSource);
}
/****
* 若需要回滚事务,在onExecute里面throw runtimeexception即可
* @param lvCallback
*/
public Exception doNamedTransactionBatch(final NamedTransCallback lvCallback){
if (dsTm==null){
dsTm=(DataSourceTransactionManager) ContextHolder.getBean("dsTransactionManager");
}
TransactionTemplate lvTt = new TransactionTemplate (dsTm);
return lvTt.execute(new TransactionCallback<Exception>() {
@Override
public Exception doInTransaction(TransactionStatus arg0) {
try {
//NamedParameterJdbcTemplate lvNamedJt=new NamedParameterJdbcTemplate(getDataSource());
NamedParameterJdbcTemplate lvNamedJt=(NamedParameterJdbcTemplate) ContextHolder.getBean("namedParameterJdbcTemplate");
lvCallback.onExecute(lvNamedJt);
return null;
} catch (Exception e) {
// 抛出异常时进行回滚
arg0.setRollbackOnly();
return e;
}
}
});
}
/****
* 若需要回滚事务,在onExecute里面throw runtimeexception即可
* @param lvCallback
*/
public Exception doTransactionBatch(final TransCallback pvCallback){
if (dsTm==null){
dsTm=(DataSourceTransactionManager) ContextHolder.getBean("dsTransactionManager");
}
TransactionTemplate lvTt = new TransactionTemplate (dsTm);
return lvTt.execute(new TransactionCallback<Exception>() {
@Override
public Exception doInTransaction(TransactionStatus arg0) {
try {
//JdbcTemplate lvJt=new JdbcTemplate(getDataSource());
JdbcTemplate lvJt=(JdbcTemplate) ContextHolder.getBean("jdbcTemplate");
//NamedParameterJdbcTemplate lvNamedJt=new NamedParameterJdbcTemplate(getDataSource());
NamedParameterJdbcTemplate lvNamedJt=(NamedParameterJdbcTemplate) ContextHolder.getBean("namedParameterJdbcTemplate");
pvCallback.onExecute(lvJt,lvNamedJt);
return null;
} catch (Exception e) {
// 抛出异常时进行回滚
arg0.setRollbackOnly();
return e;
}
}
});
}
}
将里面的lvJt换为从spring容器里取得,因为用new新建的jdbctemplate,并未被spring aop拦截。
测试结果:
17:59:28,405 DEBUG SqlLogger:43 - NamedParameterJdbcTemplate.queryForMap(..)
------FULL SQL------
select * from fun_getrecseq_range(timestamp '01/Apr/2018') as t(seqStart varchar,seqEnd varchar)
---------------------
18:00:01,280 DEBUG SqlLogger:43 - NamedParameterJdbcTemplate.queryForMap(..)
------FULL SQL------
select * from fun_getrecseq_range(timestamp '30/Apr/2018') as t(seqStart varchar,seqEnd varchar)
---------------------