文章总结
- 遇到多重事务问题时,如果不确定最好功能测试下验证结果.分析可以通过伪代码一层层分析.
- 事务传播属性是注解性事务特有的,第二个事务才会用到该属性
- 主要注意几点,抛异常代码不会往下走,只走catch和finally,源码中newTransaction很大程度上左右事务是否真正提交或回滚,此外还有rollbackOnly
- 事务是与线程绑定的,存储在ThreadLocal里
- required_new是完全创建一个新事务,解除旧ThreadLocal的绑定,自己执行完又恢复旧的事务数据
- nest是创建回滚点,异常时按照回滚点提交后抛异常
测试代码
在AllService中,事务方法save()调用了另外两个类的事务方法userService.saveUser(),cityservice.saveCity()
@Service
public class AllService {
@Autowired
private UserService userService;
@Autowired
private Cityservice cityservice;
@Transactional
public String save(){
int i = userService.saveUser();
int i1 = cityservice.saveCity();
System.out.println(this);
return ((i==1?"User ":"")+(i1==1?"City":"")+" success");
}
}
@Service
public class Cityservice {
@Autowired
private ServiceMapper mapper;
@Transactional
public int saveCity(){
City city = new City();
city.setName("德玛西亚");
city.setNum("864523");
return mapper.saveCity(city);
}
}
@Service
public class UserService {
@Autowired
private ServiceMapper mapper;
@Transactional
public int saveUser(){
User user = new User();
user.setAge("100");
user.setName("派克");
user.setIntroduction("水鬼");
return mapper.saveUser(user);
}
}
public interface ServiceMapper {
@Insert("insert into city(name, num) values(#{name}, #{num})")
int saveCity(City c);
@Insert("insert into user(name, age, introduction) values(#{name}, #{age}, #{introduction})")
int saveUser(User u);
}
事务代理源码
TransactionInterceptor.class的invoke(),最终走下面逻辑
AllService事务代理伪代码
注意catch住异常后会继续抛异常throw ex
public String save(){
代理开启事务createTransactionIfnecessary
try {
int i = userService.saveUser();
int i1 = cityservice.saveCity();
}catch (Throwable ex){
代理事务回滚
throw ex;
}
代理事务提交commitTransactionAfterReturning
}
继续替换伪代码
public String save(){
代理开启事务createTransactionIfnecessary
try {
=====================================cityservice.saveUser()开始===========================================
代理开启事务createTransactionIfnecessary
try {
saveUser()真实代码逻辑;
}catch (Throwable ex){
代理事务回滚
throw ex;
}
代理事务提交commitTransactionAfterReturning
=====================================cityservice.saveUser()结束===========================================
=====================================cityservice.saveCity()开始===========================================
代理开启事务createTransactionIfnecessary
try {
saveCity()真实代码逻辑;
}catch (Throwable ex){
代理事务回滚
throw ex;
}
=====================================cityservice.saveCity()结束===========================================
}catch (Throwable ex){
代理事务回滚
throw ex;
}
代理事务提交commitTransactionAfterReturning
}
spring传播属性
事务传播案例
正常流程
获取连接对象
@Autowired
private DataSource dataSource;
@Transactional
public String save(){
ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(connectionHolder);//打印出连接对象验证是否同一连接对象,mybatis也是这种方法获取连接对象
....
}
事务源码分析
AllService.save()第一个开启事务流程如上篇博客源码流程一样, 执行到userService.saveUser()第二次开启事务则有部分不同
- 连接对象是从ThreaLocal里获取的, 事务对象newConnectionHolder为false
- 根据传播属性(也就是说只有第二个事务及以后才会用到传播属性)修改部分参数, newTransaction为false,
该标识意味着该事务对象不能回滚和提交, 得等待跟随最外层事务对象决定回滚和提交
DataSourceTransactionManager.class
AbstractPlatformTransactionManager.class
- 提交时,newTransaction为false,所以738行不会进,不提交事务,回滚时831行也不会进,不回滚
cityservice.saveCity()第三次开启事务与第二次一样
小结
saveUser()抛异常
@Transactional
public int saveUser(){
ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(connectionHolder);
User user = new User();
user.setAge("100");
user.setName("派克");
user.setIntroduction("水鬼");
int i = mapper.saveUser(user);
if (true) throw new RuntimeException();//异常
return i;
}
小结
saveCity()抛异常
@Transactional
public int saveCity(){
ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(connectionHolder);
City city = new City();
city.setName("德玛西亚");
city.setNum("864523");
int i = mapper.saveCity(city);
if (true) throw new RuntimeException();//异常
return i;
}
小结
saveCity()抛异常, save()try-catch------坑
@Transactional
public String save(){
try {
ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(connectionHolder);
int i = userService.saveUser();
int i1 = cityservice.saveCity();
System.out.println(this);
return ((i==1?"User ":"")+(i1==1?"City":"")+" success");
}catch (Exception e){
System.out.println("我吃掉异常,不往外抛");
return "false";
}
}
从下面伪代码看, 由于26行会吃掉异常,导致30行捕获不到异常,事务正常提交,应该都成功, 但是结果所有都失败
public String save(){
代理开启事务createTransactionIfnecessary
try {
真实业务try{
=====================================cityservice.saveUser()开始===========================================
代理开启事务createTransactionIfnecessary
try {
真实业务 saveUser()真实代码逻辑;
}catch (Throwable ex){
代理事务回滚
throw ex;
}
代理事务提交commitTransactionAfterReturning
=====================================cityservice.saveUser()结束===========================================
=====================================cityservice.saveCity()开始===========================================
代理开启事务createTransactionIfnecessary
try {
真实业务 saveCity()真实代码逻辑;
}catch (Throwable ex){
代理事务回滚
throw ex;
}
=====================================cityservice.saveCity()结束===========================================
真实业务 }catch (Throwable ex){
真实业务 System.out.println("我吃掉异常,不往外抛");
真实业务 }
}catch (Throwable ex){
代理事务回滚
throw ex;
}
代理事务提交commitTransactionAfterReturning
}
源码分析
saveCity()抛异常时,在回滚时,虽然不回滚代码,但设置了rollbackOnly属性为true,该标识意味着不能提交,只能回滚
AbstractPlatformTransactionManager.calss
DataSourceTransactionManager.class
save()由于被真实业务代码捕获异常,导致事务代码没捕获异常,正常提交,走提交代码时会判断rollbackOnly,最终走回滚代码
AbstractPlatformTransactionManager.class
可能spring为了防止你catch后忘记抛异常吧,所以伪代码的注意点如下
public String save(){
代理开启事务createTransactionIfnecessary,
/根据ThreaLocal有无连接对象,判断是否新建连接对象,标识newConnectionHolder为true和false/
/根据是否是第一个事务和传播属性,标识newTransaction为true和false/
try {
int i = userService.saveUser();
int i1 = cityservice.saveCity();
}catch (Throwable ex){
代理事务回滚
/设置rollbackOnly为true/
/newTransaction如果false,不回滚/
throw ex;
}
代理事务提交commitTransactionAfterReturning
/newTransaction如果false,不提交/
/rollbackOnly为true,走回滚代码/
}
小结
saveCity()抛异常并且()try-catch
@Transactional
public int saveCity(){
int i = 0;
try {
ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(connectionHolder);
City city = new City();
city.setName("德玛西亚");
city.setNum("864523");
i = mapper.saveCity(city);
if (true) throw new RuntimeException();
}catch (Exception e){
System.out.println("我吃掉异常,不往外抛");
return i;
}
return i;
}
}
小结
saveUser()传播属性new
AllService.save()第一个开启事务流程如上篇博客源码流程一样, 执行到userService.saveUser()第二次开启事务则有部分不同
源码分析
- 挂起逻辑,保存事务管理器的属性和ThreadLocal的绑定后清空
- 又回到开启新事务的逻辑
- 由于是新事务,可以提交,然后走finally逻辑,将旧的事务恢复,恢复事务管理器的属性,恢复ThreadLocal的绑定
小结
saveUser()传播属性nested,saveCity()传播属性nested并且抛异常------坑
- nested就是回滚点的意思,回滚点设计是希望回滚时按照回滚点回滚,所以按常理说saveCity抛异常,应该只回滚saveCity,但结果是全部回滚
源码分析
AllService.save()第一个开启事务流程如上篇博客源码流程一样, 执行到userService.saveUser()第二次开启事务则有部分不同
创建回滚点
ConnectionHolder.class
提交commitTransactionAfterReturning时,在判断newTransaction前会去释放去掉回滚点
第三次开启事务和第二次一样,但由于抛了异常,走回滚逻辑completeTransactionAfterThrowing,在判断newTransaction前会回滚到混滚点,然后释放去掉回滚点,抛异常
小结
save()try-catch,saveUser()传播属性nested,saveCity()传播属性nested并且抛异常
@Transactional
public String save(){
try {
ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(connectionHolder);
int i = userService.saveUser();
int i1 = cityservice.saveCity();
System.out.println(this);
return ((i==1?"User ":"")+(i1==1?"City":"")+" success");
}catch (Exception e){
System.out.println("我吃掉异常,不往外抛");
return "false";
}
}
小结
事务传播级别 抛异常 业务逻辑catch异常 提交流程 最终结果
save() required默认 catch 新建连接,saveCity()异常被真实业务代码catch,事务没catch到,走提交逻辑,提交时saveUser()一起提交 提交
saveUser() nested 同一个连接,创建userSavepoint,走committTAR,删除userSavepoint,由于newTransaction为false,commitTAR时不提交 提交
saveCity() nested RuntimeException 同一个连接,创建citySavepoint,捕获到异常,走回滚逻辑,回滚到回滚点citySavepoint,删除citySavepoint,再往外抛异常 回滚