關於SSM框架項目使用oracle/mysql數據庫時攔截解析SQL語句

主要用到的jar包:

<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.18</version>
		</dependency>
	<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.47</version>
		</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"})
//@Component  //如果是在springboot中使用就用該註釋
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
    {
         //記錄sql執行開始時間
       long startTime = System.currentTimeMillis();
        try
        {
            // 開始執行sql
            return invocation.proceed();
        }
        catch(Exception e)
        {
            logger.error(e.getMessage(), e);
        }finally {
            SystemProps.THREAD_POOL.execute(new HandleSqlThread(invocation, System.currentTimeMillis() - startTime));
        }
        return null;
    }

    @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 long timeConsuming;// sql執行的耗時 單位:毫秒

    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語句
        logger.info("------本此執行sql[耗時:{}ms]", timeConsuming);
        SqlInfo sqlInfo = SqlParseStatements.getSqlInfo(showSql(configuration, boundSql), this.isParse);
        if(sqlInfo != null && !SqlParseStatements.SELECT.equals(sqlInfo.getMode()))
        {
            logger.info("------解析後的sql參數:{}",JSONObject.toJSONString(sqlInfo));
            .......
            }
        }
    }
    /**
     * 替換指定下標的字符
     *
     * @param str        被替換字符串
     * @param index      替換下標位置
     * @param replaceStr 替換字符
     * @param size       上一次替換後的位置標識
     * @return 替換後的結果
     */
    private static String replaceIndexOf(String str, int index, String replaceStr, Integer size) {
        String s1 = str.substring(0, index);
        String s2 = str.substring(index + 1);
        //更新堆內存中的臨時size位置標識
        size += replaceStr.length();
        return s1 + replaceStr + s2;
    }
    
  	/**
     * 進行‘?’號的替換
     */
    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 (null != parameterObject && ListUtil.isNotNull(parameterMappings)) {
            // 獲取類型處理器註冊器,類型處理器的功能是進行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);
                //查詢“?”號字符替換的開始位置
                Integer size = 0;
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        String value = Matcher.quoteReplacement(getParameterValue(obj));
                        size = sql.indexOf("?", size);
                        sql = replaceIndexOf(sql, size, value, size);
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        // 該分支是動態sql
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        String value = Matcher.quoteReplacement(getParameterValue(obj));
                        size = sql.indexOf("?", size);
                        sql = replaceIndexOf(sql, size, value, size);
                    } else {
                        //打印出缺失,提醒該參數缺失並防止錯位
                        size = sql.indexOf("?", size);
                        sql = replaceIndexOf(sql, size, "缺失", size);
                    }
                }
            }
        }
        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, int isParse,long timeConsuming) {
        this.invocation = invocation;
        this.isParse = isParse;
        this.timeConsuming = timeConsuming;
    }

    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<SQLStatement> 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&lt;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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章