背景:当一个线程中,如果需要拦截所有当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.
*
* @author YangHang
*/
@Component
@Aspect
@Slf4j
public class MybatisServiceInterceptor {
/**
* Service切入点
*/
@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 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";
@Override
public 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 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();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public 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 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 {
@Bean
public 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";
}
}