?
背景:當一個線程中,如果需要攔截所有當SQL日志,然后統一發送到一個同步器,就可以實現多個數據庫實現同步主庫,在進行紅綠上線,或者灰度部署時候,可以實現生產庫與測試庫實時同步,從而達到實時可切換的效果
通過實驗:
可知道攔截器中 按照以下順序
1、before
2、after
3、AfterReturning? AfterThrowing
4、afterCommit
?
實驗步驟:
有A() B()兩個在serviceImpl下定義帶方法,順序是A中調用B
?
public void A()
{
B();}public void B()
{//Inser的操作throw new Exception("001");
}
實驗結果
當B()聲明了@Transactional,A() 沒有聲明的時候,那么B()之后,就算拋出異常,也會完美的插入執行成功。
當A()聲明了@Transactional,就算B()沒有聲明的時候,B()只要拋出異常,數據也失敗。
?
所以,只有Controller調用Service層那一個方法是否有事務,來決定之后是否有事務。
?
package com.chinamobile.scm.masterdata.interceptor;import com.chinamobile.framework.common.context.InvokeTracer;
import com.chinamobile.framework.common.context.RequestContext;
import com.chinamobile.framework.utils.CollectionUtil;
import com.chinamobile.scm.masterdata.util.ThreadsMapUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ThreadUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;import java.lang.reflect.Method;
import java.util.Deque;
import java.util.List;/*** Intercept implementation for service component.<br>** @author YangHang*/
@Component
@Aspect
@Slf4j
public class MybatisServiceInterceptor {/*** Service切入點<br>*/@Pointcut("execution(* com.chinamobile.scm.*.service.*.*(..)) || execution(* com.chinamobile.scm.service.*.*(..))")public void pointCut() {}/*** 后置異常通知*/@AfterThrowing(pointcut = "pointCut()", throwing = "e")public void throwss(JoinPoint joinPoint, Throwable e) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();System.out.println("攔截方法異常時執行 本方法內不可事務 異常:"+method.getName());}@AfterReturning("pointCut()")public void afterreturning(JoinPoint joinPoint){MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();if(TransactionSynchronizationManager.isActualTransactionActive()) {String tid = String.valueOf(Thread.currentThread().getId());List<String> stringList= ThreadsMapUtil.getSqls(tid);if(CollectionUtils.isNotEmpty(stringList)){for(String item:stringList){log.info("SQL:"+item);}}ThreadsMapUtil.removeSQL(tid);}System.out.println("注解式攔截 本方法內可提交事務 返回"+method.getName());}@After("pointCut()")public void after(JoinPoint joinPoint){MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();
//
// if(TransactionSynchronizationManager.isActualTransactionActive()) {
// TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
// @Override
// public void afterCommit() {
// System.out.println("send email after transaction commit...");
// }
//
// });
// }System.out.println("注解式攔截,結束"+method.getName());}@Before("pointCut()")public void before(JoinPoint joinPoint){Thread.currentThread().getId();MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();System.out.println("方法規則式攔截,開始:"+method.getName());}}
package com.chinamobile.scm.masterdata.interceptor;import com.alibaba.fastjson.JSON;
import com.chinamobile.scm.masterdata.util.ThreadsMapUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationManager;import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;/**Mybatis 攔截器 允許使用的插件來攔截的方法包括 Executor (update, query, flushStatements, commit, rollback, getTransaction, close,* isClosed) ParameterHandler (getParameterObject, setParameters)*/
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),@Signature(type = StatementHandler.class, method = "batch", args = { Statement.class })})
@Slf4j
@Component
public class ExecutorInterceptor implements Interceptor {/*** 是否本地*/public boolean isSavedLocat=false;/*** 根目錄*/public String sqllogpath="/Users/yh/sqllog";@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.debug("進入到開始整SQL到時候來:");Object target = invocation.getTarget();StatementHandler statementHandler = (StatementHandler)target;BoundSql boundSql = statementHandler.getBoundSql();String sql = showSql(boundSql);//boundSql.getSql();Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();// System.out.println(sql);String updatestr= JSON.toJSONString(boundSql);if(TransactionSynchronizationManager.isActualTransactionActive()) {String tid = String.valueOf(Thread.currentThread().getId());ThreadsMapUtil.setSQLs(tid, sql);}else{log.info("SQL:"+sql);}if(isSavedLocat) {writeFile(updatestr, sqllogpath);}return invocation.proceed();}@Overridepublic Object plugin(Object o) {return Plugin.wrap(o, this);}@Overridepublic void setProperties(Properties properties) {}private String getParameterValue(Object obj) {//to_timestamp(to_char(sysdate,'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD HH24:MI:SS')String value = null;if (obj instanceof String) {value = "'" + obj.toString() + "'";} else if (obj instanceof java.sql.Timestamp) {DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);value = "to_timestamp(to_char(" + formatter.format(obj) + ",'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD HH24:MI:SS')";}else if (obj instanceof Date) {DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);value = "'" + formatter.format(obj) + "'";
// System.out.println(value);} else {if (obj != null) {value = obj.toString();} else {value = "";}}return value;}public String showSql(BoundSql boundSql) {Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();String sql = boundSql.getSql().replaceAll("[\\s]+", " ");if (parameterMappings.size() > 0 && parameterObject != null) {//TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
//
// } else {Configuration configuration=new Configuration();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("\\?", getParameterValue(obj));} else if (boundSql.hasAdditionalParameter(propertyName)) {Object obj = boundSql.getAdditionalParameter(propertyName);sql = sql.replaceFirst("\\?", getParameterValue(obj));}}// }}return sql;}public void writeFile(String data,String resultfilepath){try {Date date = new Date();String path=resultfilepath+"/"+new SimpleDateFormat("yyyy-MM-dd/").format(date);File f = new File(path);if(!f.exists()){f.mkdirs(); //創建目錄}String filepath=path+ System.currentTimeMillis()+".json";File file = new File(filepath);if(!file.exists()){file.createNewFile();}FileWriter fw = null;//true:表示是追加的標志fw = new FileWriter(file, true);fw.write(data);fw.close();} catch (Exception e) {e.printStackTrace();}finally{}}
}
?
設置可以啟動攔截SQL的部分
package com.chinamobile.scm.masterdata.interceptor;import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Properties;@Configuration
public class MybatisInterceptorConfig {@Beanpublic String myInterceptor(SqlSessionFactory sqlSessionFactory) {sqlSessionFactory.getConfiguration().addInterceptor(new ExecutorInterceptor());
// sqlSessionFactory.getConfiguration().addInterceptor(executorInterceptor);
// sqlSessionFactory.getConfiguration().addInterceptor(new ParamInterceptor());
// sqlSessionFactory.getConfiguration().addInterceptor(new ResultInterceptor());return "interceptor";}
}
?