主要用到的jar包:
<dependency>
<groupId>com.alibaba
<artifactId>druid
<version>1.0.18
</dependency>
<dependency>
<groupId>com.alibaba
<artifactId>fastjson
<version>1.2.47
</dependency>
spring配置:
<bean id=”sqlLog” class=”com.xx.xxx.xx.SqlLog”/>
<bean id=”sqlSessionFactory” class=”org.mybatis.spring.SqlSessionFactoryBean”>
……
<property name=”plugins” ref=”sqlLog”/>
</bean>
SqlLog.java:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
/**
* sql拦截类
* @author qiaojun
*/
@Intercepts({
@Signature(type = Executor.class, method = “update”, args = {
MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = “query”, args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class })
})
@SuppressWarnings({“unchecked”, “rawtypes”})
public class SqlLog implements Interceptor
{
private Logger logger = LoggerFactory.getLogger(SqlLog.class);
public static final ThreadPoolExecutor THREAD_POOL = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
@Override
public Object intercept(Invocation invocation) throws Throwable
{
try
{
THREAD_POOL.execute(new HandleSqlThread(invocation, 1));
}
catch(Exception e)
{
logger.error(e.getMessage(), e);
}
// 执行完上面的任务后,不改变原有的sql执行过程,放行
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
DateUtil.java:
import java.text.ParseException;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.format.DateTimeFormat;
/**
* joda优化后的时间工具类,
*/
public class DateUtil
{
/**
* yyyyMMdd
*/
public static final String SHORT_DATE = "yyyyMMdd";
/**
* yyyy-MM-dd HH:mm:ss
*/
public static final String FULL_DATE = "yyyy-MM-dd HH:mm:ss";
/**
* HH:mm
*/
public static final String SHORT_TIME = "HH:mm";
/**
* yyyy-MM-dd
*/
public static final String DATE = "yyyy-MM-dd";
/**
* yyyyMMddHHmmss
*/
public static final String DATEFULL = "yyyyMMddHHmmss";
/**
* 检查是否是指定时间格式字符串
*
* @Description:
* @Date:2016-6-1 下午03:27:33
* @author:dinghl
*/
public static boolean isFmtDate(String date, String fmt)
{
boolean isDate = false;
if (StringUtil.isNotNullEmpty(date))
{
try
{
DateTimeFormat.forPattern(fmt).parseDateTime(date);
isDate = true;
}
catch (Exception e)
{
}
}
return isDate;
}
/**
* 正则验证日期格式,yyyymmdd或yyyy-mm-dd或yyyy/mm/dd或yyyy mm dd
* @param dateStr 时间字符串
* @author qiaojun
*/
public static boolean isFmtDate(String dateStr)
{
if (dateStr == null)
{
return false;
}
String rexp = "^((\\d{2}(([02468][048])|([13579][26]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][1235679])|([13579][01345789]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))";
Pattern pat = Pattern.compile(rexp);
Matcher mat = pat.matcher(dateStr);
return mat.matches();
}
/**
* 将指定格式字符串时间转换成DateTime时间类型
*
* @param date 时间字符串数据 例:"2015-08-10"
* @param format 转换格式 例:"yyyy-MM-dd"
*/
public static DateTime parse2DateTime(String date, String format)
{
return DateTimeFormat.forPattern(format).parseDateTime(date);
}
/**
* 将Date转换成DateTime时间类型
*/
public static DateTime parse2DateTime(Date date)
{
return new DateTime(date);
}
/**
* 将Date类型转换成类型yyyy-MM-dd HH:mm:ss字符串时间
*/
public static String fmt2FullString(Date date)
{
return fmt2String(date, FULL_DATE);
}
/**
* 将Date类型转换成类型HH:mm字符串时间
*/
public static String fmt2String(Date date)
{
return fmt2String(date, SHORT_TIME);
}
/**
* 将Date类型转换成类型yyyy-MM-dd字符串时间
*/
public static String fmt2ShortString(Date date)
{
return fmt2String(date, DATE);
}
/**
* 将Date类型转换成类型自定义format格式字符串时间
* @param date 时间
* @param format 指定格式
* @author qiaojun
*/
public static String fmt2String(Date date, String format)
{
if(null != date)
{
return DateFormatUtils.format(date, format);
}
return null;
}
/**
* 将data字符串转换为date,转换效率比上面的方法要高一些
* @param dateString date字符串
* @param format 指定格式
* @author qiaojun
*/
public static Date parse2Date(String dateString, String format)
{
if(dateString==null || format==null)
{
return null;
}
Date date = null;
try
{
date = DateUtils.parseDate(dateString, new String[] { format });
}
catch (Exception e){}
return date;
}
/**
* 得到当前时间与目标时间相差的天数
* @param dateStr 时间字符串
* @param format 指定时间格式
* @return 返回相差天数,结果向上取整,例:超过1天算两天
* @author qiaojun
*/
public static int getDayNumber(String dateStr, String format)
{
Date date = parse2Date(dateStr, format);
if (null == date)
{
return 0;
}
long ms = (date.getTime());
return (int)Math.ceil(Math.abs((float) ms - System.currentTimeMillis()) / (1000*3600*24));
}
/**
* 比较两个时间的相差的天数
* @param sDate 开始时间
* @param eDate 结束时间
* @return 相差天数
*/
public static int twoTimeDifferenceDays(Date sDate, Date eDate)
{
if(null == sDate || null == eDate)
{
return 0;
}
return (int)(Math.abs((float) sDate.getTime() - eDate.getTime()) / (1000*3600*24));
}
/**
* 获取剩余时间秒数
*/
public static int getLeftTime(Date edate)
{
if (null == edate)
{
return 0;
}
DateTime stime = DateTime.now();
DateTime etime = new DateTime(edate);
Duration d = new Duration(stime, etime);
return (int) d.getStandardSeconds();
}
}
SqlInfo.java:
import java.util.Map;
/**
* 解析后需要的sql信息
* @author qiaojun
* @date 2018-3-19 0019
*/
public class SqlInfo
{
private String tableName;//表名
private String mode;//语句类型
private Map<String,Object> map;//字段及字段值组成的map
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
}
HandleSqlThread.java:
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
/**
* 处理sql线程
* @author qiaojun
* @date 2018-3-29 0029
*/
public class HandleSqlThread implements Runnable
{
private Logger logger = LoggerFactory.getLogger(HandleSqlThread.class);
private Invocation invocation; //拦截到的mybatis执行的sql信息等
private String taskUrl; //调用task的请求地址
private int isParse; //0:解析sql 1:不解析sql
@Override
public void run()
{
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 获取xml中的一个select/update/insert/delete节点,主要描述的是一条SQL语句
Object parameter = null;
//获取参数,if语句成立,表示sql语句有参数,参数格式是map形式
if (invocation.getArgs().length > 1)
{
parameter = invocation.getArgs()[1];
//logger.info("------parameter = {}", JSONObject.toJSONString(parameter));
}
// 获取到节点的id,即sql语句的id
//logger.info("------mapperMethod = {}", mappedStatement.getId());
// BoundSql就是封装myBatis最终产生的sql类
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
// 获取节点的配置
Configuration configuration = mappedStatement.getConfiguration();
// 获取到最终的sql语句
SqlInfo sqlInfo = SqlParseStatements.getSqlInfo(showSql(configuration, boundSql), this.isParse);
if(sqlInfo != null && !SqlParseStatements.SELECT.equals(sqlInfo.getMode()))
{
logger.info("------解析后的sql参数:{}",JSONObject.toJSONString(sqlInfo));
.......
}
}
}
/**
* 进行‘?’号的替换
*/
private static String showSql(Configuration configuration, BoundSql boundSql)
{
// 获取参数
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// sql语句中多个空格都用一个空格代替
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null)
{
// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
// 如果根据parameterObject.getClass()可以找到对应的类型,则替换
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()))
{
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
}
else
{
// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,
// 主要支持对JavaBean、Collection、Map三种类型对象的操作
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings)
{
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName))
{
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
}
else if (boundSql.hasAdditionalParameter(propertyName))
{
// 该分支是动态sql
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
}
else
{
//打印出缺失,提醒该参数缺失并防止错位
sql=sql.replaceFirst("\\?","缺失");
}
}
}
}
return sql;
}
/**
* 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理
*/
private static String getParameterValue(Object obj)
{
String value = null;
if (obj instanceof String)
{
value = "'" + obj.toString() + "'";
}
else if (obj instanceof Date)
{
value = "to_date('" + DateUtil.fmt2String((Date) obj, DateUtil.FULL_DATE) + "','yyyy-MM-dd hh24:mi:ss')";
}
else
{
if (obj != null)
{
value = obj.toString();
}
else
{
value = "''";
}
}
return value;
}
public HandleSqlThread(Invocation invocation, String taskUrl) {
this.invocation = invocation;
this.taskUrl = taskUrl;
}
public HandleSqlThread(Invocation invocation, int isParse) {
this.invocation = invocation;
this.isParse = isParse;
}
public int getIsParse() {
return isParse;
}
public void setIsParse(int isParse) {
this.isParse = isParse;
}
public Invocation getInvocation() {
return invocation;
}
public void setInvocation(Invocation invocation) {
this.invocation = invocation;
}
public String getTaskUrl() {
return taskUrl;
}
public void setTaskUrl(String taskUrl) {
this.taskUrl = taskUrl;
}
}
InsertBean.java
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLUnionQuery;
import java.util.List;
/**
*
* @author qiaojun
* @date 2018-3-16 0016
*/
public class InsertBean
{
private SQLExprTableSource tableSource;
private List<SQLIdentifierExpr> columns;
private SQLInsertStatement.ValuesClause values;
private SQLSelect query;
private List<SQLExpr> myValue;
private SQLSelectQueryBlock queryDetail ;
public SQLSelectQueryBlock getQueryDetail() {
return queryDetail;
}
public void setQueryDetail(SQLSelectQueryBlock queryDetail) {
this.queryDetail = queryDetail;
}
public SQLSelect getQuery() {
return query;
}
public void setQuery(SQLSelect query) {
this.query = query;
if (query != null){
getUnionQuery(query);
}
}
/**
* 封装批量插入时的数据
*/
private void getUnionQuery(SQLSelect select){
if(select.getQuery() instanceof SQLSelectQueryBlock) {
//只有一条数据
this.queryDetail = (SQLSelectQueryBlock) query.getQuery();
}
if(select.getQuery() instanceof SQLUnionQuery){
//多个数据
SQLUnionQuery sqlUnionQuery = (SQLUnionQuery) select.getQuery();
this.queryDetail = (SQLSelectQueryBlock) sqlUnionQuery.getLeft();
}
}
public SQLExprTableSource getTableSource() {
return tableSource;
}
public void setTableSource(SQLExprTableSource tableSource) {
this.tableSource = tableSource;
}
public List<SQLIdentifierExpr> getColumns() {
return columns;
}
public void setColumns(List<SQLIdentifierExpr> columns) {
this.columns = columns;
}
public SQLInsertStatement.ValuesClause getValues() {
return values;
}
public void setValues(SQLInsertStatement.ValuesClause values) {
this.values = values;
if (values != null){
this.myValue = values.getValues();
}
}
public List<SQLExpr> getMyValue() {
return myValue;
}
public void setMyValue(List<SQLExpr> myValue) {
this.myValue = myValue;
}
}
SqlParseStatements.java
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.oracle.visitor.OracleSchemaStatVisitor;
import com.alibaba.druid.stat.TableStat;
import com.alibaba.druid.util.JdbcConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* sql解析类
* @author qiaojun
* @date 2018-3-19 0019
*/
public class SqlParseStatements
{
private static Logger logger = LoggerFactory.getLogger(SqlParseStatements.class);
public static final String SELECT = "select";
/**
* 验证是否纯数字
*/
public static boolean isDigit(String digit)
{
boolean flag = false;
try
{
Pattern webIdPattern = Pattern.compile(“^\d+$”);
Matcher matcher = webIdPattern.matcher(digit);
flag = matcher.matches();
}
catch (Exception e)
{
flag = false;
}
return flag;
}
/**
* 解析sql
* @param sql 需要解析的sql语句
* @param isParse 0:解析sql 1:不解析sql
* @return 解析结果
*/
public static SqlInfo getSqlInfo(String sql, int isParse)
{
if(sql==null)
{
return null;
}
String parseSql;
try {
parseSql = SQLUtils.format(sql, JdbcConstants.ORACLE);
}catch (Exception e){
parseSql = sql;
logger.error(e.toString());
}
logger.info(“——格式化后的sql如下:\n{}”, parseSql);
if(isParse > 0)
{
return null;
}
List stmtList ;
try {
stmtList = SQLUtils.parseStatements(sql, JdbcConstants.ORACLE);
}catch (Exception e){
sql = sql.trim().toLowerCase();
if(sql.indexOf(SELECT) == 0)
{
return null;
}
stmtList = subStrSql(sql);
if(stmtList == null)
{
return null;
}
}
//记录需要返回的信息
SqlInfo sqlInfo = null;
for (SQLStatement stmt : stmtList)
{
OracleSchemaStatVisitor visitor = new OracleSchemaStatVisitor();
stmt.accept(visitor);
Map<TableStat.Name, TableStat> tabmap = visitor.getTables();
Map<String,Object> map = new HashMap<String,Object>();
String mode; //操作数据库的方法
if(tabmap.size() <= 0)
{
return null;
}
for(TableStat.Name name : tabmap.keySet())
{
mode = tabmap.get(name).toString().toLowerCase();
if (SELECT.equals(mode))
{
//如果是查询语句就跳过,因为该map无序,不保证会按照sql层级获取操作类型
continue;
}
sqlInfo = new SqlInfo();
sqlInfo.setTableName(name.toString().toLowerCase());
sqlInfo.setMode(mode);
if ("insert".equals(mode))
{
InsertBean bean = new InsertBean();
BeanUtils.copyProperties(stmt, bean);
if(null != bean.getQueryDetail())
{
//批量插入处理
if(bean.getColumns().size() != bean.getQueryDetail().getSelectList().size())
{
sqlInfo.setMap(map);
return sqlInfo;
}
for (int i = 0, len = bean.getColumns().size(); i < len; i++)
{
String value = bean.getQueryDetail().getSelectList().get(i).toString();
if (isDigit(value))
{
map.put(String.valueOf(bean.getColumns().get(i)).toLowerCase(), value);
}
}
}
else
{
//单个插入处理
for (int i = 0, len = bean.getColumns().size(); i < len; i++)
{
String value = bean.getMyValue().get(i).toString();
if (isDigit(value))
{
map.put(String.valueOf(bean.getColumns().get(i)).toLowerCase(), value);
}
}
}
sqlInfo.setMap(map);
return sqlInfo;
}
}
if(sqlInfo == null)
{
return null;
}
//是delete或者update语句,就会执行到这里来
for(TableStat.Condition condition:visitor.getConditions())
{
if (condition.getValues().size() == 1 && isDigit(condition.getValues().get(0) + ""))
{
map.put(condition.getColumn().getName().toLowerCase(), condition.getValues().get(0));
}
}
sqlInfo.setMap(map);
}
return sqlInfo;
}
/**
* 将sql中无法解析的条件过滤删掉
*/
private static List<SQLStatement> subStrSql(String sqlLowerCase)
{
String and = " and ";
if(!sqlLowerCase.contains(and))
{
return null;
}
int whereLength = 6;
String where = "where ";
String whereSql = sqlLowerCase.substring(sqlLowerCase.indexOf(where) + whereLength);
String[] ands = whereSql.split(and);
StringBuilder andSql = new StringBuilder(sqlLowerCase.substring(0, sqlLowerCase.indexOf(where) + whereLength));
for(String andStr :ands)
{
andStr.trim();
if(andStr.indexOf("(") == 0 &&andStr.indexOf(")") == (andStr.length() - 1))
{
continue;
}
andSql.append(" ").append(andStr).append(and);
}
andSql = new StringBuilder(andSql.substring(0, andSql.length() - 5));
List<SQLStatement> stmtList;
try {
stmtList = SQLUtils.parseStatements(andSql.toString(), JdbcConstants.ORACLE);
}catch (Exception e){
return null;
}
return stmtList;
}
}