场景描述
数据库操作需求:先查询是否存在配置,如果不存在则insert至两张表中。数据库操作的Bean中有些一个查询两个insert,查询不需要事务,insert需要事务。开发时将三者封装至一个save方法中。代码如下
public boolean save(DTO dto) {
// 查询配置
PO po = query();
save(dto, po);
}
@Transactional(rollbackFor = Exception.class)
public boolean insert(DTO dto, PO po) {
boolean isSuccess = false;
if (po == null) {
po = insert(ConvertUtil.convert2Group(dto));
}
if (po.getId() != null) {
Item item = new Item();
item.setGroupId(po.getId());
int effectCnt = insert(item);
isSuccess = effectCnt > 0;
if (!isSuccess) {
log.error("insert packFullOrder failed ,packFullOrder={}", packFullOrder);
throw new PackFullOrderException("保存待打包订单失败");
}
}
return isSuccess;
}
事与愿违,调用save方法事务没有生效,但是调用insert方法事务是有效的。由于事务的实现是通过拦截器反射实现事务操作的bean,代理的方法如果没有事务注解属性的配置会导致调用的子方法的事务失效。
- save方法是切面的切入点,切入点判断当前方法不存在事务属性配置,于是没有开启事务
- 切面执行完拦截方法后直接调用切入点ReflectiveMethodInvocation.invokeJoinpoint。从源码中可以看到此处的调用是target实际实例的方法调用,而不是代理的bean,所以此时的insert的调用是没有切面的事务拦截器进行拦截增强的。
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 反射调用实际实例方法
return invokeJoinpoint();
}
// 事务拦截器拦截调用织入事务代码
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
protected Object invokeJoinpoint() throws Throwable {
return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}
总结
使用了事务的方法,如果通过同样的实例中非事务的方法调用,会导致事务注解的实例方法的事务会不生效
解决
- 为业务接口中每一个数据库操作增加事务注解
- 业务层单独调用查询方法后再去调用事务方法