事务传播性——代码演示版超级详细
事务属性传播性
传播性 | 描述 |
---|---|
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
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因此会被锁住,就会报错,超时了)
解决的方式是
第一种情况 内部方法不处理异常
第一个案例
@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;
}
}
结论:外部发生了异常,内部也会跟着回滚
第二个案例
@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;
}
}
结论:如果内部发生了异常,外部进行了一个处理,那么外部就不会回滚
解释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();
}