创建一个Spring数据库,在Spring数据库中创建tb_account(账户表),并初始化数据。
此时执行test3方法,可以看到结果
我们发现张玮的账户中金额减少了500元,而张益达的账户中并没有增加500元。 这是因为AccountServiceImpl中故意写了int i=1/0这样的异常,所以张玮的账户减少了500元后,出现了异常,张益达的账户增加500元就没有执行。
我们在需要进行事务控制的方法上加上@Transactional,Spring就会自动帮我们进行事务的提交和回滚。
Spring 默认只有抛出运行时异常(即 RuntimeException 及子类)或 Error 及子类时,才会回滚事务 。
我们创建一个编译期异常,程序抛出异常,运行之后发现事务仍然提交了 ,解决办法:配置 rollbackFor = Exception.class
表示:遇到所有异常都回滚
通过日志我们发现,执行事务操作的对象是JdbcTransactionManager对象 ,并且为com.itheima.service.impl包下的AccountServiceImpl类中的transfer创建了事务。
JdbcTransactionManager继承了DataSourceTransactionManager,
DataSourceTransactionManager中的dobegin()方法表示开始事务,doCommit方法表示提交事务,doRollback表示回滚事务。
一旦我们进行了事务管理,也就是我们在Spring管理的类中或者方法上加了@Transactional注解,我们从容器中获取到的service就不是目标对象了,而是代理对象,其内部通过AOP的方式对目标对象进行了增强,代理对象内部使用JdbcTransactionManager对象在切点方法执行前后进行事务管理
以上面的AccountServiceImpl为例,在AccountServiceImpl中的transfer方法上面加了@Transactional注解,那么Spring就会为AccountServiceImpl创建代理对象,我们从Spring容器中拿到的就不是AccountServiceImpl这个目标对象,而是AccountServiceImpl的代理对象。
执行下面的测试方法
获得结果:
发现是通过SpringCGLIB的技术创建了 AccountServiceImpl的代理对象。
在AccountServiceImpl中的transfer方法上面加了@Transactional注解,那么Spring就会为AccountServiceImpl创建代理对象。AccountServiceImpl的代理对象会重写AccountServiceImpl中的transfer方法,然后在AccountServiceImpl的代理对象中使用2.1中介绍的JdbcTransactionManager中的dobegin方法创建事务,doCommit方法提交事务,doRollback方法回滚事务。
JdbcTransactionManager中的这3个方法相当于通知方法(增强方法),分别位于AccountServiceImpl中切点方法前后,来完执行事务。
分析:自己 try-catch 异常,意味着代理对象认为没有发生异常,因此也会提交事务。
解决办法:业务方法内不要捕获异常、或者将捕获的异常重新抛出。
众所周知,java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。@Transactional注解只有用在public方法上面才会生效。
在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。
spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,也不能添加事务功能。
如果某个方法是static的,同样无法通过动态代理,变成事务方法。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void add(UserModel userModel) {
userMapper.insertUser(userModel);
updateStatus(userModel);
}
@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}
由上面的代码我们可以看见在add方法中调用了updateStatus方法,updateStatus方法上使用了@Transactional注解,这种同一个类中的方法直接内部调用,会导致事务失效。
我们知道Spring中的事务是通过创建目标对象的代理对象,来进行事务控制。add方法中的updateStatus(userModel);相当于this.updateStatus(userModel);相当于本类对象去调用updateStatus(userModel);而不是通过代理对象去调用updateStatus(userModel); 所以就会导致事务失效。
解决办法:
1、在该Service类中注入自己(因为使用@Autowired prvate ServiceA serviceA;依赖注入的是代理对象)
可能会出现循环依赖的问题,具体的解决方法参考Spirng02中解决循环依赖。
2、 新加一个Service方法
通过新增一个ServiceB,并在ServiceB中的doSave方法里面依次执行add方法和update方法,并在ServiceA的save方法中使用ServiceB的对象调用dosave方法。(@Transactional注解加在ServiceB的dosave方法上)
这种在方法上加了@Transactional注解,但是类上没有加上类似@Service注解的,事务也不会生效,因为Spring的事务本身是基于SpringAop的。
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
roleService.doOtherThing();
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
我们在 UserService的add的方法上使用了@Transactional注解,并在add方法中调用了roleService.doOtherThing();
RoleService中的doOtherThing()方法上也使用了@Transactional注解,并且propagation = Propagation.NESTED
propagation表示传播的意思,Propagation.NESTED表示事务嵌套,这个在下面的事务的传播方式中会讲解。
这样就造成了在add方法中调用了roleService.doOtherThing();而在RoleService中的doOtherThing()方法上事务的传播行为定义为嵌套,就造成了方法的嵌套。
这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。但事实是,insertUser也回滚了。
因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
解决办法
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。
Spring事务失效的场景详见:
spring事务(注解 @Transactional )失效的12种场景_@transactional超时会不会抛出异常-CSDN博客
Spring中事务的传播行为默认的是REQUIRED,常见的就是REQUIRED和REQUIRES_NEW
创建一个Service1,并在a方法上加上@Transactional注解,并在a方法中使用Service2的对象调用Service2中的b方法。
创建一个Service2,并在b方法上加上@Transactional注解,那么在Service1中的a方法调用Service2中的b方法,使用的是a的事务还是b的事务呢?
看日志得到答案
1、首先创建Service1中a方法的事务 (Creating new transaction with name.......)
2、Service1.a().....(执行Service1中的a方法)
3、Participating in existing transaction(加入已经存在的事务,也就是Service1中a方法的事务)
4、 Service2.b().....(执行Service2中的b方法)
上面演示的在a方法中调用同样加了@Transactional注解的b方法就是事务的传播,通过日志我们得出结论Spring中事务的传播行为默认的是REQUIRED(需要事务,有则加入,无则创建新事务 ),所以上面的例子中使用的是Service1中的a方法的事务。
也就是说Service1中的a方法和Service2中的b方法同时使用Service1中的a方法,属于同意事务。
要想Service2中的b方法重新创建一个事务,只需要在Service2中的b方法上的@Transactional注解里面加上REQUIRES_NEW即可。
再来查看日志
1、首先创建Service1中a方法的事务 (Creating new transaction with name.......)
2、Service1.a().....(执行Service1中的a方法)
3、Suspending current transaction,creating new transaction with...(暂停a方法中的事务,创建b方法的事务)
4、 Service2.b().....(执行Service2中的b方法)
在Service2中的b方法上的@Transactional注解里面加上REQUIRES_NEW,表示需要新事务,无论有无,总是创建新事务,所以当Service1中的a方法调用Service2中的b方法时,也会创建Service2中b方法的事务。
此时Service2中的b方法和Service1中的a方法是两个不同的事务,他们之间是相互独立的。