@Transactional 常用属性和失效的几种情况

常用属性

propagation:事务传播行为

举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了

参考链接:https://blog.csdn.net/hhb442/article/details/134980521

@Service
public class TopService {

    @Autowired
    private StudentService studentService;
    
    //测试 事务的传播行为
    @Transactional
    public void topService(){
        studentService.changeAge();
        studentService.changeName();
    }
}

@Service
public class StudentService {
    @Autowired
    private StudentDao studentDao;

    //事务的传播行为
    @Transactional
    public void changeAge(){
        studentDao.updateAgeById(998, 1);
    }
    //事务的传播行为,
    @Transactional
    public void changeName(){
        studentDao.updateNameById("dasdas", 1);
        int i = 1/0;
    }

}

默认Propagation.REQUIRED(如果父方法有事务,就加入父方法事务,如果没有就新建自己独立的事务)

下面代码中是父方法有事务的情况,propagation 设置为Propagation.REQUIRED,在topService()中调用了studentService.changeAge()和studentService.changeName(),因为事务传播行为为REQUIRED,所以changeAge()和changeName()方法在同一个事务中。

此时changeName()发生运行时异常,两个方法同时回滚, 年龄和名字均不会被修改。

@Service
public class TopService {

    @Autowired
    private StudentService studentService;
    
    //测试 事务的传播行为
    @Transactional
    public void topService(){
        studentService.changeAge();
        studentService.changeName();
    }
}

@Service
public class StudentService {
    @Autowired
    private StudentDao studentDao;

    //事务的传播行为
    @Transactional(propagation = Propagation.REQUIRED)
    public void changeAge(){
        studentDao.updateAgeById(998, 1);
    }
    //事务的传播行为,
    @Transactional(propagation = Propagation.REQUIRED)
    public void changeName(){
        studentDao.updateNameById("dasdas", 1);
        int i = 1/0;
    }

}

Propagation.REQUIRES_NEW(不管父方法是否有事务,我都新建事务,都是独立的,子方法都是独立的事务)

不管父方法是否有事务,我都新建事务,都是独立的,子方法都是独立的事务

下面代码中,propagation 设置为Propagation.REQUIRES_NEW,在topService()中调用了studentService.changeAge()和studentService.changeName(),因为事务传播行为为REQUIRES_NEW,所以changeAge()和changeName() 子方法是两个独立的事务

此时changeName()发生运行时异常,changeName()发生回滚,不会影响changeAge()方法,年龄将被修改,名字不会修改。

@Service
public class TopService {

    @Autowired
    private StudentService studentService;
    
    //测试 事务的传播行为
    @Transactional
    public void topService(){
        studentService.changeAge();
        studentService.changeName();
    }
}

@Service
public class StudentService {
    @Autowired
    private StudentDao studentDao;

    //事务的传播行为
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void changeAge(){
        studentDao.updateAgeById(998, 1);
    }
    //事务的传播行为,
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void changeName(){
        studentDao.updateNameById("dasdas", 1);
        int i = 1/0;
    }

}
isolation:隔离级别,mysql默认是Isolation.REPEATABLE_READ(可重复读)
rollbackFor:异常回滚,rollbackFor = Exception.class,默认是RuntimeException 和 Error ,这里改成了exception,因为FileNotFoundException
属于 Exception异常,如果用默认设置遇到文件找不到不会回滚。

 

失效情况

第一种 Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用.

原因:@Transactional是基于动态代理实现的,不是public的方法不会创建代理对象

第二种 在类内部调用调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启

 
@Component  
public class TestServiceImpl implements TestService {  
    @Resource  
    TestMapper testMapper;  
  
    @Transactional  
    public void fun2() {  
          
    }
@Transactional  
public void fun1(){ //类内部调用@Transactional标注的方法。 

fun2();
}
}

原因:在类的内部调用内部方法是通过this对象调的,相当于this.fun2(),没有走动态代理,

解决办法:1 获取当前类的代理对象: ITestService proxy=(ITestService) AopContext.currentProxy();2 通过代理对像调@transactional方法 : proxy.fun2()

第三种 事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚

  
@Component  
public class TestServiceImpl implements TestService {  
    @Resource  
    TestMapper testMapper;  
  
    @Transactional  
    public void insertTestCatchException() {  
        try {  
            xxxx
//运行期间抛异常
throw new NeedToInterceptException("");

}catch (Exception e){ System.out.println(""); //catch捕获了Exception 异常,但没抛出异常
} } } 

 第四中 多线程/异步场景

@Transactional
public void doSomething() {
    new Thread(() -> {
        // 这个事务不起作用
        updateData();
    }).start();
}

 

@Transactional在一个方法调用另一个方法场景下情况说明

情况1:fun方法调同类下的fun2方法,通过this调用(不走代理),Spring 的@Transactional注解默认基于动态代理实现,只有通过代理对象调用被注解的方法,事务才能生效;如果是方法内部直接调用(本类内方法互相调用),会绕过代理对象,被调用方法的@Transactional注解会失效(即使fun2()加了该注解也没用)。(@Transactional注解失效不代表被注解的方法没事务)这种情况如果fun2抛异常,会导致fun2和fun中的修改都回滚,如果fun2没有异常,fun抛出异常,fun和fun2也都会回滚,因为fun和fun2在同一个事务中。

@Service
public class MyService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 被@Transactional注解修饰,事务生效
    @Transactional
    public void fun() {
        // 1. fun()中的数据库修改操作
        jdbcTemplate.update("UPDATE user SET name = 'fun修改' WHERE id = 1");
        
        // 2. 调用本类内部的fun2()方法(内部调用,绕过代理)
        fun2();//fun2没有自己开启事务,也会加入到fun的事务中
    }

    // 本类内部方法,无论是否加@Transactional都不影响整体事务(因为是内部调用)
    public void fun2() {
        // 3. fun2()中的数据库修改操作
        jdbcTemplate.update("UPDATE user SET name = 'fun2修改' WHERE id = 2");
        
        // 抛出运行时异常,向上传播到fun()方法
        throw new RuntimeException("fun2执行异常,触发回滚");
    }
}

  情况2:如果fun()方法中try-catch 捕获了fun2()抛出的异常,且未重新抛出,那么 Spring 的事务拦截器无法感知到异常的发生,会认为事务执行成功,进而提交事务,导致fun()fun2()中已执行的数据库修改被持久化。

@Service
public class MyService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void fun() {
        // 1. fun()中的数据库修改操作
        jdbcTemplate.update("UPDATE user SET name = 'fun修改' WHERE id = 1");
        
        try {
            // 2. 调用本类内部的fun2()方法
            fun2();
        } catch (Exception e) {
            // 捕获异常但未重新抛出,吞掉了异常
            System.out.println("捕获fun2异常:" + e.getMessage());
        }
    }

    public void fun2() {
        // 3. fun2()中的数据库修改操作
        jdbcTemplate.update("UPDATE user SET name = 'fun2修改' WHERE id = 2");
        
        throw new RuntimeException("fun2执行异常,本应触发回滚");
    }
}

结果:事务正常提交,fun()fun2()中的数据库修改都会生效,不会回滚。

如果fun2加的是@Transactional(propagation = Propagation.REQUIRES_NEW),fun中未捕获fun2的异常,fun2抛出异常fun会回滚吗

答案是:fun2()的事务会回滚,且fun()的事务也会跟着回滚(整体都回滚),因为fun2的异常会往上抛到fun中,fun不处理继续上抛,直到被spring事务拦截器捕获导致fun的事务也被回滚.

 

 

 
 
posted @ 2023-04-10 16:59  杨吃羊  阅读(198)  评论(0)    收藏  举报