事务属性传播性

传播性 描述
REQUIRED 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务(默认)
SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
MANDATORY 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
REQUIRES_NEW 创建一个新的事务,如果当前存在事务,则把当前事务挂起
NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起
NEVER 以非事务方式运行,如果当前存在事务,则抛出异常
NESTED 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED

解释:REQUIRED

第一种情况

第一个案例

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    @Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        userService2.update();
        int i = 1/0;
    }

}
@Service
public class UserService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional
    public void update(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"lisi");
        //int i = 1/0;
    }
}

测试类

    @Test
    void contextLoads() {
        userService.transfer();
    }

控制台执行报错,数据回滚,是因为update方法在transfer方法内,而这个transfer方法出现了报错,而造成了回滚。

另外由REQUIRED 解释来看 update方法虽然有事务,但是他不是自己的事务,而是因为transfer自身有事务,update的事务就加入到transfer里面去,所以,transfer报错,都会回滚

另外下一个案例同理

同上面的代码transfer 中的int i = 1/0;方法注释掉,update()方法的int i = 1/0;开启,一样能造成回滚

第二案例

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    @Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        userService2.update();
        //int i = 1/0;
    }

}
@Service
public class UserService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional
    public void update(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"lisi");
        int i = 1/0;
    }
}
@RestController
public class HelloController {
    @Autowired
    UserService userService;
    @GetMapping("/hello")
    public void hello(){
        userService.transfer();
    }
}

用的是apifox来进行测试 http://localhost:8080/hello

image-20220807142117517.png

Creating new transaction with name [org.javaboy.spring_tran04.UserService.transfer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

事务传播性——代码演示版超级详细-小白菜博客
创建名称为 [org.javaboy.spring_tran04.UserService.transfer] 的新事务:PROPAGATION_REQUIRED(事务),ISOLATION_DEFAULT(隔离)

这句话的意思就是说 只有org.javaboy.spring_tran04.UserService.transfer 开启了PROPAGATION_REQUIRED级别的事务 (transfer 方法)。另外ISOLATION_DEFAULT隔离级别也就是个默认的级别(也就是说,将 Spring 的事务隔离级别设置为 ISOLATION—DEF AULT 时, Spring 不做事务隔离级别的处理,会直接使用数据库默认的事务隔离级别。)

事务传播性——代码演示版超级详细-小白菜博客
Participating in existing transaction

意思就是transfer 已经开启了事务,而update方法就加入到这个事务中来了

第二种情况

第一个案例

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    //@Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        userService2.update();
        //int i = 1/0;
    }

}
@Service
public class UserService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional
    public void update(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"lisi");
        int i = 1/0;
    }
}
@RestController
public class HelloController {
    @Autowired
    UserService userService;
    @GetMapping("/hello")
    public void hello(){
        userService.transfer();
    }
}

依旧使用APIfox来发送请求 http://localhost:8080/hello

事务传播性——代码演示版超级详细-小白菜博客
此时数据就有了,但是只有一个生效了

此时的控制台打印

Creating new transaction with name [org.javaboy.spring_tran04.UserService2.update]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

意思是:只有UserService2中的update方法的事务开启了,默认的事务,默认的隔离级别

总结

如果一个方法开启了事务,内部的方法也开启了事务,那么内部的方法的事务是无效的,都由外部的事务说了算。

如果外部方法未开启事务,而内部的方法开启了事务,那么外部不受内部的事务控制

解释REQUIRES_NEW

跟REQUIRED不同的是:

REQUIRED如果内外部都有事务,那么内部的事务就不会开启,而都是跟着外部的事务走

REQUIRES_NEW如果内外部都有事务,可以理解为,创建了两个事务,内部的事务是一个独立的事务

先举个例子,A方法开启了事务,而内部有个方法B,B也开启了事务
如果A方法抛出了异常,并不一定会造成B也回滚了,因为B是一个独立的事务,
如果B也抛出了异常分至于会不会影响A回滚会有两种情况:首先B肯定会回滚
1.如果B抛出了异常,并且处理了,那么A就不会回滚,
2.如果B未处理异常,那么A就会回滚

测试之前,有个前提,就是给数据库的username字段加上索引

要不然会报出错误,锁等待超时

(就是在执行A的时候,整个表被锁住了,未提及事务,B因此会被锁住,就会报错,超时了)

解决的方式是

image-20220807151327737.png

第一种情况 内部方法不处理异常

第一个案例

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    @Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        userService2.update();
        //int i = 1/0;
    }

}
@Service
public class UserService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void update(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"lisi");
        //int i = 1/0;
    }
}
@RestController
public class HelloController {
    @Autowired
    UserService userService;
    @GetMapping("/hello")
    public void hello(){
        userService.transfer();
    }
}

结果money都变成了1000!

事务传播性——代码演示版超级详细-小白菜博客
第二个案例

首先把数据库里面的zhangsan 和lisi 的Money 都设置为1;

只改变UserService的代码

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    @Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        userService2.update();
        int i = 1/0;
    }

}

数据库发生了改变

事务传播性——代码演示版超级详细-小白菜博客
第三个案例

首先把数据库里面的zhangsan 和lisi 的Money 都设置为1;

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    @Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        userService2.update();
        //int i = 1/0;
    }

}

@Service
public class UserService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void update(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"lisi");
        int i = 1/0;
    }
}

数据都会回滚

数据库里面的zhangsan 和lisi 的Money 都为1;

第二种情况 内部方法处理了异常

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    @Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        try {
            userService2.update();
        }catch (Exception e){
            //e.printStackTrace();
        }
        //int i = 1/0;
    }

}
@Service
public class UserService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void update(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"lisi");
        int i = 1/0;
    }
}

数据库的money前两个都设置为1

当发送请求

事务传播性——代码演示版超级详细-小白菜博客
因为处理了异常,外部的不会滚,内部的会回滚

解释NESTED

NESTED 表示:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。

第一个案例

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    @Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        userService2.update();
        int i = 1/0;
    }

}
@Service
public class UserService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional(propagation = Propagation.NESTED)
    public void update(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"lisi");
        //int i = 1/0;
    }
}

结论:外部发生了异常,内部也会跟着回滚

image-20220807154707009.png

第二个案例

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    UserService2 userService2;
    @Transactional
    public void transfer(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"zhangsan");
        try {
            userService2.update();
        }catch (Exception e){
            e.printStackTrace();
        }
        //int i = 1/0;
    }

}
@Service
public class UserService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional(propagation = Propagation.NESTED)
    public void update(){
        jdbcTemplate.update("update user1 set money = ? where username=?",1000,"lisi");
        int i = 1/0;
    }
}

结论:如果内部发生了异常,外部进行了一个处理,那么外部就不会回滚

image-20220807155036253.png

解释MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常

就是说A有事务,B的事务是MANDATORY 那么B的事务就加入到A中。如果A没有事务,那么就报错了

解释SUPPORTS

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行

见名知意

解释NOT_SUPPORTED

以非事务方式运行,如果当前存在事务,则把当前事务挂起

SUPPORTS 表示如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

也就是说内部肯定是不会回滚的,就算内部报错了,内部也不会回滚,假如外部报错了,外部处理了异常,那么外部不会回滚,反之

解释NEVER

以非事务方式运行,如果当前存在事务,则抛出异常

回滚规则

默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)以及 Error 时才会回滚,在遇到检查型(Checked Exception)异常时不会回滚。

像 1/0,空指针这些是 RuntimeException,而 IOException 则算是 Checked Exception,换言之,默认情况下,如果发生 IOException 并不会导致事务回滚。

如果我们希望发生 IOException 时也能触发事务回滚,那么可以按照如下方式配置:

@Transactional(rollbackFor = IOException.class)
public void handle2() {
    jdbcTemplate.update("update user set money = ? where username=?;", 1, "zhangsan");
    accountService.handle1();
}