1. 程式人生 > >自定義切面會吃掉異常,導致事務不生效的問題。

自定義切面會吃掉異常,導致事務不生效的問題。

問題描述

我們都知道,我們定義一個切面,然後繫結一個切點後,這個切面就能在合適的時間自動切入切點。對於@AfterThrowing@Around,我們可以再切面中捕獲異常,處理異常。
我們也知道,我們對一個方法新增事務,那麼當此方法丟擲異常後,事務會捕獲異常,自動執行混滾。

那麼,如果新增事務的方法就是我們說的那個切點呢?這個方法(切點)在同時添加了事務和切面的情況下,如果這個方法丟擲了異常,程式碼會怎麼執行呢?

對結果的猜測

程式碼執行的效果無非以下三種情況:

1. 切面執行,事務不執行;
2. 切面不執行,事務執行;
3. 切面和事務都執行;

我們對這三種猜測進行如下的驗證。

驗證

1、建立一張表

CREATE TABLE `tb_test` (
  `username` varchar(12) NOT NULL,
  `password` varchar(12) NOT NULL,
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2、定義一個pojo

import java.io.Serializable;

public class MyTestDO implements Serializable {
    private static
final long serialVersionUID = 1431573779444162048L; private String userName; private String password; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return
password; } public void setPassword(String password) { this.password = password; } }

3、寫dao層程式碼

//定義dao介面:
 boolean insertTest(MyTestDO myTestDO);
#配置mapper.xml
<insert id="insertTest" parameterType="MyTestDO">
        INSERT INTO tb_test (username, password)
        values (#{userName}, #{password});
    </insert>

4、直接在controller層呼叫dao層程式碼

    @RequestMapping(value = "/daoTest", method = RequestMethod.GET)
    public boolean daoTest() {
        MyTestDO myTestDO = new MyTestDO();
        myTestDO.setUserName("wei.hu");
        myTestDO.setPassword("123456");

        return stubbingDao.insertTest(myTestDO);
    }

5、讓程式碼run起來(本地啟的8088埠),呼叫http://localhost:8088/daoTest,可以看到返回true,證明準備工作ready。

到這裡,我們準備工作已經做好了,我們可以在此基礎上進行驗證了。

6、我們先來驗證切面和事務同時存在的情況。
6.1、新增宣告式事務:

//修改controller類,在方法上新增 @Transactional

@RequestMapping(value = "/daoTest", method = RequestMethod.GET)
@Transactional
public boolean 
    MyTestDO myTestDO = new MyTestDO();
    myTestDO.setUserName("wei.hu");
    myTestDO.setPassword("123456");

    return stubbingDao.insertTest(myTestDO);
}

6.2、在啟動類(這裡使用的是springboot)上添加註解 @EnableTransactionManagement

@SpringBootApplication
@ServletComponentScan
@MapperScan(basePackages = "cn.***.mockcloud.store.dao")
@EnableTransactionManagement
public class MockcloudApplication {

    public static void main(String[] args) {
        SpringApplication.run(MockcloudApplication.class, args);
    }
}

到這裡,宣告式事務已經新增完成。

7、我們開始為controller類的daoTest()方法新增切面。
7.1、定義一個切面類ConfigControllerAop,並將controller類的daoTest()方法作為切入點:

package cn.tongdun.mockcloud.container.aop;

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ConfigControllerAop {

    private static final Logger logger = LoggerFactory.getLogger(ConfigControllerAop.class);

    @Pointcut("execution(* cn.tongdun.mockcloud.web.controller.*.*(..))")
    private void controllerPointcut() {
    }

    @AfterThrowing(throwing = "e", pointcut = "controllerPointcut()")
    public void exceptionDeal(Exception e) {
        e.printStackTrace();
        logger.error("[MOCKCLOUD - ConfigControllerAop - exceptionDeal]");
    }

}

8、為了驗證問題,我們連續向庫中寫入兩條一樣的記錄。因為庫表中username是惟一的,因此連續兩條插入動作,第二條插入動作將會拋異常。我們將controller類的daoTest()方法的方法體修改為如下:

@RequestMapping(value = "/daoTest", method = RequestMethod.GET)
@Transactional
public boolean daoTest() {
    MyTestDO myTestDO = new MyTestDO();
    myTestDO.setUserName("wei.hu");
    myTestDO.setPassword("123456");

    System.out.println(stubbingDao.insertTest(myTestDO));

    return stubbingDao.insertTest(myTestDO);
}

9、讓程式碼run起來,呼叫http://localhost:8088/daoTest。然後我們檢視資料庫,發現庫表中寫入了一條資料。

這代表什麼?如果說事務生效,那麼庫表中應該是一條資料都沒有的(我們隊controler類的daoTest()方法添加了@Transactional宣告式事務,同時又在daoTest()方法中新增連續對資料庫做了兩次insert動作。第一次insert成功,第二次失敗)。但是我們在庫表中確確實實插入了一條資料,並且應用後臺丟擲了異常。

因此,在對同一個方法同時新增事務和切面,當發生異常時,事務是不生效的!!!

10、我們重置實驗條件(刪除上面操作在庫中插入的資料),註釋掉切面程式碼,然後從新run程式碼,執行’http://localhost:8088/daoTest‘,就會發現,庫表中一條資料都沒有,此時事務生效了。

結論

如果一個方法,不僅作為切點添加了切面,而且還添加了事務,那麼當這個方法丟擲異常時,總是切面先捕獲到異常,並且吞掉了這個異常。而事務是捕獲不到這個異常的,因此事務是不生效的。