• Spring事务的传播行为分析


    前言

    最近项目有涉及到Spring事务,所以工作之余,想认真了解学习下Spring事务,查阅了若干资料,做了一个demo(PS:参考了大牛的)。

    现分享总结如下:

    1、Spring 事务的简介

    理解事务之前,

    先讲一个你日常生活中最常干的事:取钱。 
    比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱;然后ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的。 
    事务就是用来解决类似问题的。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。 
    在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。 
    事务有四个特性:ACID

    • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
    • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
    • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
    • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

     具体可以参考:http://www.mamicode.com/info-detail-1248286.html

    2、事务的传播行为

    事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

    1、PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

    2、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘

    3、PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

    4、PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

    5、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

    7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

    下面我们来看下代码:

    3、demo分析

    事先插入一条记录

      @Override
        public void before() {
            jdbcTemplate.update("INSERT  INTO USER (name,password) VALUES (?,?)","xiang","11111112");
        }

      3.1、PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。


    @Override
    public void txRollbackInnerTxRollbackPropagationRequires() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { jdbcTemplate.update("INSERT INTO USER (name,password) VALUES (?,?)","Huang","1111231"); transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { jdbcTemplate.update("insert into user name, password) values (?, ?)", "Huang", "1111112"); //内部事务设置了 setRollbackOnly transactionStatus.setRollbackOnly(); } }); } }); }

    测试结果:

     /**
         * PROPAGATION_REQUIRES:内部事务设置了 {@link org.springframework.transaction.TransactionStatus#setRollbackOnly()} 来触发回滚,
         * 外部事务接受到了一个 {@link UnexpectedRollbackException} 也被回滚
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationRequires() throws Exception {
            try {
                springTxService.txRollbackInnerTxRollbackPropagationRequires();
            } catch (UnexpectedRollbackException e) {
    
            }finally {
                Assert.assertEquals(1,springTxService.mysqlConnection());
            }
        }

    3.2、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

      @Override
        public void txRollbackInnerTxRollbackPropagationSupports() {
            supportsTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                    throw new CustomRuntimeException();
                }
            });
        }
    
        @Override
        public void txRollbackInnerTxRollbackPropagationSupports2() {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                    supportsTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus status) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                    "Huang", "1111112");
                            status.setRollbackOnly();
                        }
                    });
                }
            });
        }

    测试用例:

     /**
         * PROPAGATION_SUPPORTS:如果当前事务上下文中没有事务,
         * 那么就按照没有事务的方式执行代码
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationSupports() throws Exception {
            try {
                springTxService.txRollbackInnerTxRollbackPropagationSupports();
            } catch (CustomRuntimeException e) {
                e.printStackTrace();
            }finally {
                Assert.assertEquals(2, springTxService.mysqlConnection());
    
            }
    
        }
        /**
         * PROPAGATION_SUPPORTS:如果当前事务上下文中存在事务,
         * 那么合并到当前上下文的事务中去,
         * 表现地和 {@link org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED} 一样
         */
        @Test(expected = UnexpectedRollbackException.class)
        public void testTxRollbackInnerTxRollbackPropagationSupports2() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationSupports2();
        }

    3.3、PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

     @Override
        public void txRollbackInnerTxRollbackPropagationMandatory() {
            mandatoryTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                }
            });
        }
    
        @Override
        public void txRollbackInnerTxRollbackPropagationMandatory2() {
            nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                    mandatoryTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                    "Huang", "1111112");
                            //内部事务回滚了,外部事务也跟着回滚
                            transactionStatus.setRollbackOnly();
                        }
                    });
                }
            });
        }

    测试用例:

      /**
         * PROPAGATION_MANDATORY:强制性的事务,当前的事务上下文中不存在事务的话,会抛出 {@link IllegalTransactionStateException}
         */
        @Test(expected = IllegalTransactionStateException.class)
        public void testTxRollbackInnerTxRollbackPropagationMandatory() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationMandatory();
    
        }
        /**
         * PROPAGATION_MANDATORY:强制性的事务,内部的事务发生回滚,
         * 那么外部的事务也会发生回滚,表现地和 {@link org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED}
         * 一样,也会抛出 {@link UnexpectedRollbackException}
         */
        @Test(expected = UnexpectedRollbackException.class)
        public void testTxRollbackInnerTxRollbackPropagationMandatory2() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationMandatory2();
        }

    3.4、PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

     @Override
        public void txRollbackInnerTxRollbackPropagationRequiresNew() {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                    "Huang", "1111112");
                        }
                    });
                    //外部事务发生回滚,内部事务应该不受影响还是能够提交
                    throw new RuntimeException();
                }
            });
        }
    
        @Override
        public void txRollbackInnerTxRollbackPropagationRequiresNew2() {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                    requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                    "Huang", "1111112");
                            // 内部事务发生回滚,但是外部事务不应该发生回滚
                            transactionStatus.setRollbackOnly();
                        }
                    });
                }
            });
        }
    
        @Override
        public void txRollbackInnerTxRollbackPropagationRequiresNew3() {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                    requiresNewTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                    "Huang", "1111112");
                            // 内部事务抛出 RuntimeException,外部事务接收到异常,依旧会发生回滚
                            throw new RuntimeException();
                        }
                    });
                }
            });
        }

    测试用例:

    /**
         * PROPAGATION_REQUIRES_NEW:外部事务发生回滚,内部事务继续提交,不受影响
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationRequiresNew() throws Exception {
            try {
                springTxService.txRollbackInnerTxRollbackPropagationRequiresNew();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                Assert.assertEquals(2,springTxService.mysqlConnection());
            }
        }
        /**
         * PROPAGATION_REQUIRES_NEW:内部事务通过设置 {@link org.springframework.transaction.TransactionStatus#setRollbackOnly()} 来触发回滚,
         * 外部事务依旧可以不受影响,正常提交
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationRequiresNew2() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationRequiresNew2();
            Assert.assertEquals(2,springTxService.mysqlConnection());
        }
        /**
         * PROPAGATION_REQUIRES_NEW:内部事务抛出了 {@link RuntimeException} 异常发生了回滚,外部事务接收到这个异常也会发生回滚
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationRequiresNew3() throws Exception {
            try {
                springTxService.txRollbackInnerTxRollbackPropagationRequiresNew3();
            } catch (RuntimeException e) {
                e.printStackTrace();
            }finally {
                Assert.assertEquals(1,springTxService.mysqlConnection());
    
            }
        }

    3.5、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

     @Override
        public void txRollbackInnerTxRollbackPropagationNotSupport() {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                    notSupportedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus status) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                    "Huang", "1111112");
                        }
                    });
                    // 外部事务回滚,不会把内部的也连着回滚
                    transactionStatus.setRollbackOnly();
                }
            });
        }
    
        @Override
        public void txRollbackInnerTxRollbackPropagationNotSupport2() {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    try {
                        notSupportedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                            @Override
                            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                                jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                        "Huang", "1111112");
                                throw new CustomRuntimeException();
                            }
                        });
                    } catch (CustomRuntimeException e) {
                    }
                }
            });
        }

    测试用例:

     /**
         * PROPAGATION_NOT_SUPPORTED:不支持事务,
         * 外围的事务回滚不会导致它包含的内容回滚
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationNotSupport() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationNotSupport();
            Assert.assertEquals(2,springTxService.mysqlConnection());
    
        }
        /**
         * PROPAGATION_NOT_SUPPORTED:不支持事务,内部发生异常,外部捕获,都不会发生回滚
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationNotSupport2() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationNotSupport2();
            Assert.assertEquals(3,springTxService.mysqlConnection());
        }

    3.6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

     1   @Override
     2     public void txRollbackInnerTxRollbackPropagationNever() {
     3         neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
     4             @Override
     5             protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
     6                 jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
     7                         "1111112");
     8             }
     9         });
    10     }
    11 
    12     @Override
    13     public void txRollbackInnerTxRollbackPropagationNever2() {
    14         transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    15             @Override
    16             protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
    17                 jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
    18                         "1111112");
    19                 neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
    20                     @Override
    21                     protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
    22                         jdbcTemplate.update("insert into user (name, password) values (?, ?)",
    23                                 "Huang", "1111112");
    24                     }
    25                 });
    26             }
    27         });
    28     }
    29 
    30     @Override
    31     public void txRollbackInnerTxRollbackPropagationNever3() {
    32         neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
    33             @Override
    34             protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
    35                 jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
    36                         "1111112");
    37                 neverTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
    38                     @Override
    39                     protected void doInTransactionWithoutResult(TransactionStatus status) {
    40                         jdbcTemplate.update("insert into user (name, password) values (?, ?)",
    41                                 "Huang", "1111112");
    42                     }
    43                 });
    44             }
    45         });
    46     }
    View Code

    测试代码:

     /**
         * PROPAGATION_NEVER:不允许当前事务上下文中存在事务,如果没有,就正常执行
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationNever() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationNever();
            Assert.assertEquals(2,springTxService.mysqlConnection());
        }
        /**
         * PROPAGATION_NEVER:不允许当前事务上下文中存在事务,
         * 如果有,则抛出 {@link IllegalTransactionStateException}
         */
        @Test(expected = IllegalTransactionStateException.class)
        public void testTxRollbackInnerTxRollbackPropagationNever2() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationNever2();
        }
        /**
         * PROPAGATION_NEVER:不允许当前事务上下文中存在事务,
         * 当两个 NEVER 的嵌套在一起的时候,应该也是能够执行成功的。
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationNever3() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationNever3();
            Assert.assertEquals(3,springTxService.mysqlConnection());
        }

    3.7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

     @Override
        public void txRollbackInnerTxRollbackPropagationNested() {
            nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                    nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                                    "1111112");
                            // 内部事务设置了 rollbackOnly,外部事务应该不受影响,可以继续提交
                            transactionStatus.setRollbackOnly();
                        }
                    });
                }
            });
        }
    
        @Override
        public void txRollbackInnerTxRollbackPropagationNested2() {
            nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    jdbcTemplate.update("insert into user (name, password) values (?, ?)", "Huang",
                            "1111112");
                    nestedTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
                        @Override
                        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                            jdbcTemplate.update("insert into user (name, password) values (?, ?)",
                                    "Huang", "1111112");
                        }
                    });
                    // 外部事务设置了 rollbackOnly,内部事务应该也被回滚掉
                    transactionStatus.setRollbackOnly();
                }
            });
        }

    测试代码:

    /**
         * PROPAGATION_NESTED:内部事务通过设置 {@link org.springframework.transaction.TransactionStatus#setRollbackOnly()} 来触发回滚,
         * 外部事务依旧可以不受影响,正常提交
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationNested() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationNested();
            Assert.assertEquals(2, springTxService.mysqlConnection());
    
        }
        /**
         * PROPAGATION_NESTED:外部事务通过设置 {@link org.springframework.transaction.TransactionStatus#setRollbackOnly()} 来触发回滚,由于
         * savepoint 在外部事务的开头,所以内部事务应该也会被一起回滚掉
         */
        @Test
        public void testTxRollbackInnerTxRollbackPropagationNested2() throws Exception {
            springTxService.txRollbackInnerTxRollbackPropagationNested2();
            Assert.assertEquals(1, springTxService.mysqlConnection());
    
        }
  • 相关阅读:
    EasyNVS摄像机公网全终端无插件网页摄像机直播管理服务运行出现“请求服务不存在或已停止”
    EasyNVR摄像机网页直播之问题解决:Failed_SYSTEM_CurrentCont......_Application_EasyNVR_Service registry key alr
    EasyNVR智能云终端硬件盒子x86版自我维护之摄像机网页直播系统基础运维
    EasyNVR网页无插件播放摄像机RTSP流是如何调取接口在Web页实现多窗口同时直播的
    EasyNVR网页Chrome无插件播放安防摄像机视频流是怎么做到web浏览器延时一秒内
    EasyNVR摄像机网页H5全平台无插件直播流媒体播放服务二次开发之接口鉴权示例讲解
    EasyNVR摄像机网页Chrome无插件视频播放功能二次开发之通道配置文件上传下载示例代码
    EasyNVR网页H5无插件播放摄像机视频功能二次开发之直播通道接口保活示例代码
    EasyNVR网页Chrome无插件播放摄像机视频功能二次开发之云台控制接口示例代码
    如何通过设备探索获取EasyNVR、EasyDSS所需要的摄像机rtsp地址
  • 原文地址:https://www.cnblogs.com/Fly-Bob/p/7347206.html
Copyright © 2020-2023  润新知