spring事務管理之踩坑一

先來說下該博文的創作背景,要從一道經典的面試(網傳是)說起下面貼上源碼

    @Autowired
    ITestMapper testMapper;
    
    public void parent(){

        child();
        User user = new User();
        user.setName("lgh").setMobile("18813140000");
        testMapper.insert(user);
    }
    
    public void child(){
        User user = new User();
        user.setName("lgh-1").setMobile("18813140001");
        testMapper.insert(user);
        throw new ServiceException("主動拋出異常");
    }

示例中兩個方法要求如何改造使得child() 方法不能影響parent() 方法的執行,換句話說就是正常情況下保證parent() 方法的正常執行,而child() 無關緊要。

然後就各顯神通廢話不多說,出現啦下面這個看似正確完美的版本,直接貼源碼:

    @Transactional
    public void parent(){

        try {
            child();
        } catch (Exception e) {
            log.error("child插入異常-{}"+e);
        }
        User user = new User();
        user.setName("lgh").setMobile("18813140000");
        testMapper.insert(user);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void child(){
        User user = new User();
        user.setName("lgh-1").setMobile("18813140001");
        testMapper.insert(user);
        throw new ServiceException("主動拋出異常");
    }

事實證明執行完畢之後,parent 和 child 方法都成功啦,通過日誌發現child 的事務並沒有創建執行,也就是說 child 的事務沒有起作用,因爲spring的事務是通過AOP實現的,說到底也就是jdk 的動態代理來實現的。我們就模擬了一下上面的場景通過動態代理來說明這個問題所在,jdk 的動態代理是通過接口實現的源碼如下:

public class TestServiceImpl implements TestService {

    @Override
    public void parent() {
        child();
        System.out.println("parent method invoke start");
    }

    @Override
    public void child() {
        System.out.println("child method invoke start");
    }

    @Override
    public void test() {
        System.out.println("test method invoke start");
    }
}

代理調用實現:

public class TestHandler implements InvocationHandler {


    private Object target; // 目標對象 真實對象
    public TestHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().startsWith("parent") || method.getName().startsWith("child")){
            System.out.println("事務處理中*******************");
        }
        return method.invoke(target, args);
    }

    public static void main(String[] args) {
        TestHandler handler = new TestHandler(new TestServiceImpl());
        TestService service = (TestService) Proxy.newProxyInstance(TestHandler.class.getClassLoader(), new Class[]{TestService.class}, handler);
        service.parent();
    }
}

通過代理調用parent 方法,內部調用 child 方法,日誌打印如下:

事務處理中*******************
child method invoke start
parent method invoke start

也就是說調用parent 方法的時候是代理類去調用的進而打印啦事務處理的日誌,但是child 方法的調用並沒有走代理類,通過打印該對象 this 得出結論。再parent 方法被代理類調用的時候,內部調用child 方法的對象是目標源對象,而不是代理對象。這樣就明朗很多,之所以child 的事務註解失效是因爲 child 的調用不是 代理的調用而是源目標對象本身的調用。

如果想實現child 的事務註解生效,顯而易見需要使用代理類去調用child 方法,實現如下:

    @Transactional
    public void parent(){

        try {
            ((TestService)AopContext.currentProxy()).child();
        } catch (Exception e) {
            log.error("child插入異常-{}"+e);
        }
        User user = new User();
        user.setName("lgh").setMobile("18813140000");
        testMapper.insert(user);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void child(){
        User user = new User();
        user.setName("lgh-1").setMobile("18813140001");
        testMapper.insert(user);
        throw new ServiceException("主動拋出異常");
    }

說到這呢幫大家再回顧下事務的傳播屬性及隔離級別:請參考https://blog.csdn.net/qq_39470733/article/details/105217133

如有披露或問題歡迎留言或者入羣探討

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章