众所周知,通过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)
---------------------