spring service作日志切面后,事务竟然会失效?

KerryLi 发布于 2017/08/24 16:12
阅读 1K+
收藏 0

在servvice层的某个方法用@transactional开启事务,

也在service的方法用自定义注解开启日志切面,

结果,service层报异常,事务不回滚。

去除方法上的日志切面注解,事务又生效了....... 这什么鬼? 难道是日志切面影响了事务?

关键代码如下

service  class  

@Service
public class UserServiceImpl implements UserService{

	@Autowired
	private UserMapper userMapper;
	
	@MethodLog(module="用户",funtion="添加用户")
	@Transactional
	@Override
	public void insert123(User user) {
		// TODO Auto-generated method stub
		userMapper.insert(user);
		throw new NullPointerException("nullpoint exception!!!!!");
		
	}
}
@Pointcut("@annotation(com.kerry.aop.MethodLog)")
	private void pointcut() {}
	
	
	@Around("pointcut()")
    public void around(ProceedingJoinPoint point) throws Throwable {
		AppLog logEntity=new AppLog();
		long beginTime = System.currentTimeMillis();//1、开始时间
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
		logEntity.setId(UUID.randomUUID().toString().replace("-", ""));
		ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request=attributes.getRequest();
		
		StringBuffer paramStr=new StringBuffer();
		Enumeration<String> nameList=request.getParameterNames();
		while(nameList.hasMoreElements()){
			String name = nameList.nextElement();
			paramStr.append(name).append("=").append(request.getParameter(name)).append("&");
		}
		Map<String, String> map = getMethodLog(point);  
        logEntity.setPath(request.getRequestURI());
        if(paramStr.length()>0){
        	logEntity.setParam(paramStr.substring(0,paramStr.length()-1));
        }
        logEntity.setModule(map.get("module"));
        logEntity.setFunction(map.get("function"));
        try{
        	Object object=point.proceed();
        	logEntity.setStatus("0");
        	logEntity.setRemark("成功");
        }catch(Exception e){
        	e.printStackTrace();
        	logEntity.setStatus("1");
        	logEntity.setRemark("程序出异常");
        	if(e!=null){
        		logEntity.setException(getExceptionTrace(e));
        	}
        }finally{
        	long endTime = System.currentTimeMillis();//1结束时间
    		long time=endTime-startTimeThreadLocal.get();
    		logEntity.setConsumeTime(String.valueOf(time));
    		log.info("Msg:开始写入日志...");
    		logService.addAppLog(logEntity);
    		log.info("Msg:日志写入结束");
		}
        
	}

包扫描路径什么的都是对的,切面也都是对的, 都能正常执行, 就是加了切面日志后,影响了事务回滚,导致 用户记录,和日志记录都正常插入了。 

加载中
0
KerryLi
KerryLi

问题解决, 方案 1,如果在springmvc项目中可以配置  事务,和自定义切面的执行顺序

方案2 另起一个其他线程类Thread,实现日志的记录

方案一实现:

xml配置设置事务order

<!-- 配置 Annotation 驱动,扫描@Transactional注解的类定义事务  -->
	<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="2"/>
    <!-- MyBatis end -->

自定义横切面设置order

@Component
@Aspect
@Order(1)
public class AppLogAdvice {
	private Logger log=Logger.getLogger(AppLogAdvice.class);
	
	@Autowired
	private AppLogService logService;

	
	private NamedThreadLocal<Long>  startTimeThreadLocal =new NamedThreadLocal<Long>("StopWatch-StartTime");
	
	@Pointcut("@annotation(com.powercn.fcity.common.base.MethodLog)")
	private void pointcut() {}
	
	
	@Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
		AppLog logEntity=new AppLog();
		long beginTime = System.currentTimeMillis();//1、开始时间
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
		logEntity.setId(UUID.randomUUID().toString().replace("-", ""));
		ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request=attributes.getRequest();
		
		StringBuffer paramStr=new StringBuffer();
		Enumeration<String> nameList=request.getParameterNames();
		while(nameList.hasMoreElements()){
			String name = nameList.nextElement();
			paramStr.append(name).append("=").append(request.getParameter(name)).append("&");
		}
		Map<String, String> map = getMethodLog(point);  
        logEntity.setPath(request.getRequestURI());
        if(paramStr.length()>0){
        	logEntity.setParam(paramStr.substring(0,paramStr.length()-1));
        }
        logEntity.setModule(map.get("module"));
        logEntity.setFunction(map.get("function"));
        Object object = null;
        try{
        	object=point.proceed();
        	logEntity.setStatus("0");
        	logEntity.setRemark("成功");
        	return object;
        }catch(Exception e){
        	e.printStackTrace();
        	logEntity.setStatus("1");
        	logEntity.setRemark("程序出异常");
        	if(e!=null){
        		logEntity.setException(getExceptionTrace(e));
        	}
        }finally{
        	long endTime = System.currentTimeMillis();//1结束时间
    		long time=endTime-startTimeThreadLocal.get();
    		logEntity.setConsumeTime(String.valueOf(time));
    		log.info("Msg:开始写入日志...");
    		logService.addAppLog(logEntity);
    		log.info("Msg:日志写入结束");
		}
        return object;
	}

 

方案二实现如下:

@Component
@Aspect
public class AppLogAdvice {
	private Logger log=Logger.getLogger(AppLogAdvice.class);
	
	@Autowired
	private AppLogService logService;

	
	private NamedThreadLocal<Long>  startTimeThreadLocal =new NamedThreadLocal<Long>("StopWatch-StartTime");
	
	@Pointcut("@annotation(com.kerry.aop.MethodLog)")
	private void pointcut() {}
	
	
	@Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
		AppLog logEntity=new AppLog();
		long beginTime = System.currentTimeMillis();//1、开始时间
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
		logEntity.setId(UUID.randomUUID().toString().replace("-", ""));
		ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request=attributes.getRequest();
		
		StringBuffer paramStr=new StringBuffer();
		Enumeration<String> nameList=request.getParameterNames();
		while(nameList.hasMoreElements()){
			String name = nameList.nextElement();
			paramStr.append(name).append("=").append(request.getParameter(name)).append("&");
		}
		Map<String, String> map = getMethodLog(point);  
        logEntity.setPath(request.getRequestURI());
        if(paramStr.length()>0){
        	logEntity.setParam(paramStr.substring(0,paramStr.length()-1));
        }
        logEntity.setModule(map.get("module"));
        logEntity.setFunction(map.get("function"));
        Object object = null;
        try{
        	object=point.proceed();
        	logEntity.setStatus("0");
        	logEntity.setRemark("成功");
        	return object;
        }catch(Exception e){
        	e.printStackTrace();
        	logEntity.setStatus("1");
        	logEntity.setRemark("程序出异常");
        	if(e!=null){
        		logEntity.setException(getExceptionTrace(e));
        	}
        	new RuntimeException();
        }finally{
        	long endTime = System.currentTimeMillis();//1结束时间
    		long time=endTime-startTimeThreadLocal.get();
    		logEntity.setConsumeTime(String.valueOf(time));
    		log.info("Msg:开始写入日志...");
//    		logService.addAppLog(logEntity);
    		new LogThread(logService, logEntity).start();
    		log.info("Msg:日志写入结束");
		}
        return object;
	}

这样的话 就会先执行自定义的切面 AppLogAdvice, 在执行spring事务的管理, service异常 service中事务会回滚,切面中日志也会正常记录。

切面执行顺序只是springmvc中的解决方法,但是springboot中如何解决了。 springboot中貌似事务都是自动配置的,如何配置事务切面的order属性呢??

springboot中在切面类上注解@Order(1)  里面数字尽可能下,保证自定义切面先运行。

1
温安适
温安适

insert123方法上有2个切面,需要制定顺序,保证@Transactional后执行才成。

方案1 MethodLog的切面实现类,上加入@Order注解,高优先级的后执行,所以需要你设置MethodLog的优先级低。即使用默认的就可以了@Order即可,

方案2,将MethodLog中的异常向上抛出

温安适
温安适
回复 @KerryLi : 你的情况应该是想log不回滚而业务回滚是吧,要是这样就需要将Method的切面@Order(1)尝试下,让MethodLog的切面后执行
KerryLi
KerryLi
方案1 在切面类注解上@order 事务还是没回滚,刚测试了下, 方案2 异常向上抛出貌似还是没回滚,,,,
0
码农_皇甫
码农_皇甫

不能用try catch, 否则事物无法回滚

KerryLi
KerryLi
切面类不用try catch, 日志都给回滚了,,,
0
tangzhanxiang
tangzhanxiang

日志通过异步的方式写入,切换线程写入

tangzhanxiang
tangzhanxiang
放到队列里面,其他线程读取,单个和批量写入都可以
KerryLi
KerryLi
通过@async 还是都给滚回来了, user,log都滚了.........
0
藏言
藏言

log用异步的自己独立事务,异常往外抛出

KerryLi
KerryLi
回复 @KerryLi : 在@around上事务还是不会回滚,用在@AfterThrowing上才可以,,,,,,
KerryLi
KerryLi
通过新启动thread线程运行确实可以,日志正常记录,user也能回滚,,, 总感觉有点笨,这方法..........
0
rockingMan
rockingMan
@Aspect
public class LogAspect {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    private static final Logger logger = Logger.getLogger(LogAspect.class);

    @Before(value = "pointcut()")
    public void before(JoinPoint point) {
        logger.info(LogUtils.toEntranceLog(point, null));
    }

    public void after(JoinPoint joinpoint) {
    }

    @Pointcut("execution(* com.x.x.x.dubboxService.*.*(..))")
    private void pointcut(){}

    @AfterReturning(value = "pointcut()",returning = "retVal")
    public void afterReturn(JoinPoint point, Object retVal) {
        logger.info(LogUtils.toEntranceLog(point, retVal));
    }

    @AfterThrowing(value = "pointcut()" ,throwing = "throwable")
    public void afterThrow(Throwable throwable) {
        logger.error(throwable.getMessage(), throwable);
    }

}

LogService 的保存日志方法 用 REQUIRE_NEW 呢

返回顶部
顶部