情况说明
- 首先开启了AOP,并且同时开启了事务。
- 下面这个TransactionAspect就是一个简单的AOP切面,有一个Around通知。
@Aspect
@Component
public class TransactionAspect {
@Pointcut("execution(* com.qhyu.cloud.datasource.service.TransactionService.*(..))") // the pointcut expression
private void transactionLogInfo() {} // the pointcut signature
/**
* Title:around <br>
* Description:这个Around吃掉了异常 <br>
* 不太建议吃掉异常,出现这个问题的原因需要排查下为什么?
* author:candidate <br>
* date:2023/11/10 14:11 <br>
* @param
* @return
*/
@Around("transactionLogInfo()")
public Object around(ProceedingJoinPoint pjp){
Object proceed = null;
System.out.println("TransactionAspect调用目标方法前:@Around");
try {
// aop拦截器
proceed = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("TransactionAspect调用目标方法后:@Around");
return proceed;
}
}
- 创建一个Service和dao,并且需要事务。
public interface TransactionService {
void doQuery(String id);
void doUpdate(String id);
}
@Component
public class TransactionServiceImpl implements TransactionService {
@Autowired
TransactionDao transactionDao;
@Override
public void doQuery(String id) {
System.out.println(transactionDao.UserQuery(id));
}
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
@Override
public void doUpdate(String id) {
int i = transactionDao.UserUpdate(id);
System.out.println("更新用户表信息"+i+"条");
}
}
@Component
public class TransactionDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// id例子:0008cce0-3c92-45ea-957f-4f6dd568a3e2
public Object UserQuery(String id){
return jdbcTemplate.queryForMap("select * from skyworth_user where id = ?",id);
}
@SuppressWarnings({"divzero"})
public int UserUpdate(String id) throws RuntimeException{
Map<String, Object> resultMap = jdbcTemplate.queryForMap("select * from skyworth_user where id = ?", id);
int flag = 0;
if (resultMap.get("is_first_login") == Integer.valueOf("0")){
flag = 1;
}
int update = jdbcTemplate.update("update skyworth_user set is_first_login = ? where id ='0008cce0-3c92-45ea-957f-4f6dd568a3e2' ", flag);
int i=1/0;
return update;
}
}
可以看到TransactionServiceImpl的doUpdate方法是被事务管理的。万事具备,只欠东风。
- 启动
private static void transactionTest(AnnotationConfigApplicationContext annotationConfigApplicationContext) {
// 事务回滚了吗,测试事务和aop的时候使用
TransactionService bean2 = annotationConfigApplicationContext.getBean(TransactionService.class);
bean2.doQuery("0008cce0-3c92-45ea-957f-4f6dd568a3e2");
bean2.doUpdate("0008cce0-3c92-45ea-957f-4f6dd568a3e2");
bean2.doQuery("0008cce0-3c92-45ea-957f-4f6dd568a3e2");
}
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext =
new AnnotationConfigApplicationContext(AopConfig.class);
transactionTest(annotationConfigApplicationContext);
}
- 现象:事务没有回滚,is_first_login标识原来是0,出现异常后数据库显示is_first_login是1,结论就是事务失效了。
问题分析
Spring事物之@EnableTransactionManagemen一章我们说过自动代理创建器是会升级的,所以AnnotationAwareAspectJAutoProxyCreator类就是实现其代理的类。
我这儿TransactionServiceImpl是实现了接口的,并没有强制使用cglib,所以此处用的是JdkDynamicAopProxy生成的代理对象,所以只要我启动项目,就会调用invoke方法。
我需要观察的是Advice的排序问题,因为此处明显是应为Around吃掉了异常才会导致事务失效,但是有时候我们不得不使用Around吃掉异常的时候应该怎么处理就是我们要解决的问题。
所以invoke方法中this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)就是去获取所有的Advice。
ExposeInvocationInterceptor的作用就是在方法调用期间将当前代理对象设置到AopContext中。它是整个AOP拦截器链中的第一个拦截器,确保在后续的拦截器或切面中可以通过AopContext获取到当前代理对象。
所以我们需要关注的就是后面两个Interceptors:
- org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor: advice org.springframework.transaction.interceptor.TransactionInterceptor@3918c187
- InstantiationModelAwarePointcutAdvisor: expression [transactionLogInfo()]; advice method [public java.lang.Object com.qhyu.cloud.aop.aspect.TransactionAspect.around(org.aspectj.lang.ProceedingJoinPoint)]; perClauseKind=SINGLETON
advice调用过程会先调用TransactionInterceptor,然后才会调用ransactionAspect.around。
TransactionInterceptor
首先会调用TransactionInterceptor的invoke方法,代码如下:
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
// 执行被拦截的方法,也就是加了@transaction注解的方法
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
这个方法非常简单,就是执行并返回方法invokeWithinTransaction的内容。
下面是核心代码,这边进行了精简,为了方便观看。
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 声明式事务,else逻辑是编程式事务,两种不同的处理方法
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
// 执行方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
// 异常回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
当执行invocation.proceedWithInvocation()的时候将会执行新建的CoroutinesInvocationCallback() 的proceedWithInvocation方法。invocation.proceed();就会开始调用下一个。也就是我们自定义的Around。
其实看到这里就很清楚了,我们应该让Around先执行,或者让Around不吃掉异常才能让事务生效。
TransactionAspect.around
开始执行我们自定义的around方法,方法如下:
@Around("transactionLogInfo()")
public Object around(ProceedingJoinPoint pjp){
Object proceed = null;
System.out.println("TransactionAspect调用目标方法前:@Around");
try {
// aop拦截器
proceed = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("TransactionAspect调用目标方法后:@Around");
return proceed;
}
所以会先打印出第一行日志,然后执行pjp.proceed()去调用目的方法doUpdate(),但是目的方法会被吃掉异常,此时执行完成之后再打印Around的第二行日志,最后又回到invokeWithinTransaction,因为异常被吃掉了,所以就直接提交事务了。
排序问题
advice排序这一章我们分析了,order可以改变其排序,具体代码如下:
AbstractAdvisorAutoProxyCreator#findEligibleAdvisors
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// Around before after afterReturing afterThrowing
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
// 这里是通过order进行排序的
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
查看两个Interceptors的Order,都是2147483647,所以我们可以调整下顺序,让Around在前面执行 ,然后TransactionInterceptor后执行,然后抛出异常后进入到回滚逻辑,最后走Around的后续逻辑。
解决方案
- 修改我们Advice的Order
@Aspect
@Component
@Order(value = Ordered.HIGHEST_PRECEDENCE+1)
public class TransactionAspect {
-
修改@EnableTransactionManagement(order=)
-
调整后结果符合预期
TransactionAspect调用目标方法前:@Around
{id=0008cce0-3c92-45ea-957f-4f6dd568a3e2, is_first_login=1, lastlanddingtime=null, update_time=2023-10-07 09:24:45}
TransactionAspect调用目标方法后:@Around
java.lang.ArithmeticException: / by zero
at com.qhyu.cloud.datasource.dao.TransactionDao.UserUpdate(TransactionDao.java:43)
at com.qhyu.cloud.datasource.service.impl.TransactionServiceImpl.doUpdate(TransactionServiceImpl.java:33)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:128)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:413)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:123)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at com.qhyu.cloud.aop.aspect.TransactionAspect.around(TransactionAspect.java:48)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:634)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:624)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:72)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:224)
at com.sun.proxy.$Proxy34.doUpdate(Unknown Source)
at com.qhyu.cloud.QhyuApplication.transactionTest(QhyuApplication.java:88)
at com.qhyu.cloud.QhyuApplication.main(QhyuApplication.java:22)
TransactionAspect调用目标方法后:@Around