下面代码有事务么
@Service
publice class TestTran implements Tran {
@Override
public void saveTableA() {
mapper.saveA();
saveTableB();
}
@Transactional(rollbackFor = Exception.class)
public void saveTableB() {
mapper.saveB();
}
}
@RestController
public class Controller {
@Resource
private TestTran testTran;
@GetMapping("save")
public int save() {
testTran.saveTableA();
}
}
答案很显然:没有
@Transactional 生效的条件
- 除非特殊配置(比如使用 AspectJ 静态织入实现 AOP),否则只有定义在 public 方法上的 @Transactional 才能生效。原因是,Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private/protected 方法无法代理到,Spring 自然也无法动态增强事务处理逻辑。
- 必须通过代理过的类从外部调用目标方法才能生效
spring怎么让@Transactional生效的
程序中其实有两个TestTran类
- 一个是原生的类,原生的类中任何spring的注解都不会生效
- 一个是spring代理后的增强类,spring会对spring的注解的方法通过AOP进行增强,spring代理后的原生类,都是spring增强类。
所以以上代码没有生效就很显然了,直接调用使用this调用saveTableB,相当于this.saveTableB。没有使用spring代理的增强类,就不会调用到被@Transactional修饰的增强方法。就不会有事务了。
再看下面的代码 会回滚么
@Service
publice class TestTran implements Tran {
@Resource
private TranMapper mapper;
@Override
@Transactional
public void saveTableA() throws IOException {
mapper.saveA();
saveTableB();
}
@Transactional
public void saveTableB() {
throw new IOException();
}
}
@RestController
public class Controller {
@Resource
private TestTran testTran;
@GetMapping("save")
public int save() {
testTran.saveTableA();
}
}
答案很显然:不会
原因是:@Transactional 的默认rollbackFor参数是 RuntimeException
抛出IOException是不会触发增强类的
那么问题来了,增强类是怎么触发回滚的
增强方法其实就是在原方法的基础上加个try-catch
当throw异常的时候,增强类就捕获到了,执行catch,在cache里做的事务回滚操作
没有异常,就在返回前做的commit操作
所以你想手动回滚,也可以模仿这个增强类
@Service
publice class TestTran implements Tran {
@Resource
private TranMapper mapper;
@Override
@Transactional
public void saveTableA() throws IOException {
mapper.saveA();
try {
saveTableB();
} catch(IOException e) {
// 在这里手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
@Transactional
public void saveTableB() {
throw new IOException();
}
}
@RestController
public class Controller {
@Resource
private TestTran testTran;
@GetMapping("save")
public int save() {
testTran.saveTableA();
}
}
增强方法读到setRollbackOnly()这个配置的时候,就会给你回滚了
再看下面的代码 saveA会回滚么
@Service
publice class TestTran implements Tran {
@Resource
private TranMapper mapper;
@Resource
private TestTranB testTranB;
@Override
@Transactional
public void saveTableA() {
mapper.saveA();
try {
testTranB.saveTableB();
} catch(Exception e) {
// 在这里抓住
log.error("....");
}
}
}
@Service
publice class TestTranB {
@Resource
private TranMapper mapper;
@Transactional
public void saveTableB() {
throw new RuntimeException();
}
}
@RestController
public class Controller {
@Resource
private TestTran testTran;
@GetMapping("save")
public int save() {
testTran.saveTableA();
}
}
答案很显然:还是会的
你可能很惊讶,我不是抓住了B的异常的了。saveA方法没有抛出异常,为什么会回滚。
是这样的:两个方法使用了默认的事务传递级别:PROPAGATION_REQUIRED。也就是使用了同一个事务。而一个事务是有一个是否回滚标识的。在saveTableB里,已经将这个回滚标记位置为需要回滚了。
所以虽然到了saveTableA这一层已经没有异常了,因为事务的回滚属性是需要回滚所以mapper.saveA()还是会回滚。
那正确的做法是什么呢
- 在saveTableA catch里将回滚标记位置为不需要回滚。此时AB都不回滚了。但达不到A不回滚B回滚的诉求。
- 更换事务传播级别。比如使用PROPAGATION_REQUIRES_NEW。这样saveTableB会新起一个事务,两个方法的save事务独立。
@Service
publice class TestTran implements Tran {
@Resource
private TranMapper mapper;
@Resource
private TestTranB testTranB;
@Override
@Transactional
public void saveTableA() {
mapper.saveA();
try {
testTranB.saveTableB();
} catch(Exception e) {
// 在这里抓住
log.error("....");
}
}
}
@Service
publice class TestTranB {
@Resource
private TranMapper mapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveTableB() {
throw new RuntimeException();
}
}
@RestController
public class Controller {
@Resource
private TestTran testTran;
@GetMapping("save")
public int save() {
testTran.saveTableA();
}
}
这样就好了。此时A不会回滚。但B回滚。
但注意啊: 事务的隔离级别写saveTableB上