1. 程式人生 > 其它 >淺析Spring事務失效的情況、原因分析及如何解決:快取、事務、非同步不能在同一個類中相互呼叫,會失效

淺析Spring事務失效的情況、原因分析及如何解決:快取、事務、非同步不能在同一個類中相互呼叫,會失效

  專案中遇到的問題的結論,具體現象就不描述了,通過查資料,就是知道一點就行:快取、事務、非同步不能同一個類中相互呼叫,會失效(只能在別的類裡呼叫)。具體問題描述我們直接看網上的文章吧。

一、同一個類中方法呼叫,導致@Transactional失效

一、問題現象

  開發中避免不了會對同一個類裡面的方法呼叫,比如有一個類Test,它的一個方法A,A再呼叫本類的方法B(不論方法B是用public還是private修飾),但方法A沒有宣告註解事務,而B方法有。則外部呼叫方法A之後,方法B的事務是不會起作用的。這也是經常犯錯誤的一個地方:

 
    @GetMapping("/test")
    
private Integer A() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("2"); /** * B 插入欄位為 3的資料 */ int insert=insertB(); /** * A 插入欄位為 2的資料 */ int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert; } @Transactional() public Integer insertB() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("3"); cityInfoDict.setParentCityId(3); return cityInfoDictMapper.insert(cityInfoDict); }

二、註解失效原因分析

  其實這還是由於使用Spring AOP代理造成的,因為只有當事務方法被當前類以外的程式碼呼叫時,才會由Spring生成的代理物件來管理

三、解決方案

  利用spring提供的動態代理機制。

1、引入spring動態代理:

<!-- aop -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2、方法呼叫改成動態代理呼叫:

 
    @GetMapping("/test")
    private Integer A() throws Exception {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
       
       // B 插入欄位為 3的資料
        Test test = (Test)AopContext.currentProxy();
 
        int insert=test.insertB();
       
        int insert = cityInfoDictMapper.insert(cityInfoDict);
 
        return insert;
    }
 
    @Transactional()
    public Integer insertB() throws Exception {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("3");
        cityInfoDict.setParentCityId(3);
 
        return cityInfoDictMapper.insert(cityInfoDict);
    }

  可能會提示:Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

  ze在代理類上新增:@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) 問題解決!!!!!!

以上程式碼示例見:https://blog.csdn.net/zhangkaixuan456/article/details/109082645

二、Spring事務生效的情況、原理分析及如何解決

一、使用預設的事務處理方式

  因為在java的設計中,它認為不繼承RuntimeException的異常是”checkException”或普通異常,如IOException,這些異常在java語法中是要求強制處理的。對於這些普通異常,spring預設它們都已經處理,所以預設不回滾。可以新增rollbackfor=Exception.class來表示所有的Exception都回滾。

二、內部呼叫

  不帶事務的方法呼叫該類中帶事務的方法,不會回滾。因為spring的回滾是用代理模式生成的,如果是一個不帶事務的方法呼叫該類的帶事務的方法,直接通過this.xxx()呼叫,而不生成代理事務,所以事務不起作用。常見解決方法是,拆類。

  spring中在一個擁有事務的方法A中呼叫另一個會掛起事務並建立新事務的方法B,如果使用this呼叫這個方法B,此時方法B丟擲了一個異常,此時的方法B的事務會失效的,並不會回滾。

  PROPAGATION_REQUIRED:如果存在一個事務,則支援當前事務。如果沒有事務則開啟事務;

  PROPAGATION_REQUIRES_NEW:總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。

@Service
public class EmployeeService {
    @Autowired
    private EmployeeDao employeeDao;
    public void save(){
        try {        
            this.saveEmployee();  //此處this呼叫不會開啟事務,資料會被儲存
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    @Transactional(propagation = Propagation.PROPAGATION_REQUIRED)
    //此處無論是PROPAGATION_REQUIRED還是PROPAGATION_REQUIRES_NEW,事務均不生效
    public void saveEmployee(){
        Employee employee = new Employee();
        employee.setName("zhangsan");
        employee.setAge("26";
        employeeDao.save(employee);
        throw new RuntimeException();
    }
}

三、問題原因

  原因在於JDK的動態代理,只有被動態代理直接呼叫時才會產生事務。在SpringIoC容器中返回的呼叫的物件是代理物件而不是真實的物件,而這裡的this是EmployeeService真實物件而不是代理物件。

四、解決辦法:

1、方法1、在方法A上開啟事務,方法B不用事務或預設事務,並在方法A的catch中throw new RuntimeException();

  在沒指定rollbackFor時,預設回滾的異常為RuntimeException,這樣使用的就是方法A的事務。(一定要throw new RuntimeException();否則異常被捕捉處理,同樣不會回滾。)如下

@Transactional() //開啟事務
public void save(){
    try {        
        this.saveEmployee();  //這裡this呼叫會使事務失效,資料會被儲存
    }catch (Exception e){
        e.printStackTrace();
        throw new RuntimeException();
    }
}

2、方法2:方法A上不開啟事務,方法B上開啟事務,並在方法A中將 this 呼叫改成動態代理呼叫AopContext.currentProxy(),如下:

public void save(){
    try {        
        EmployeeService proxy =(EmployeeService) AopContext.currentProxy();
        proxy.saveEmployee();
    }catch (Exception e){
        e.printStackTrace();
    }
}

三、至於快取或異常是同樣的問題原因