主要用到的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<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;
}
}