spring4 之 声明式事务

来源:互联网 发布:iphone7移动数据开关 编辑:程序博客网 时间:2024/06/05 07:23

spring4 之 声明式事务

事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性。事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。

事务的四个关键属性(ACID)

  • 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.

  • 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.

  • 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事务都应该与其他事务隔离开来, 防止数据损坏.

  • 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中


Spring Tx

  • 作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制。

  • Spring 既支持编程式事务管理, 也支持声明式的事务管理。

  • 编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码。

  • 声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理。

  • Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了。

  • Spring 的核心事务管理抽象是interface PlattformTransactionManagter 它为事务管理封装了一组独立于技术的方法. 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的

实现Spring4 Tx 有很多中方式:

  • 编程式实现

    Connection conn = null;try {    conn = MySQL.getConnection();    // 设置事务提交开始    boolean autoCommit = conn.getAutoCommit();    // 设置为非自动提交    conn.setAutoCommit(false);    .    .    .    // 事务结束    conn.commit();    conn.setAutoCommit(autoCommit);    } catch (Exception e) {    conn.rollback();    log.error(e.getMessage());    // 程序异常 40206    ret = -6;}finally{    conn.close();        }
  • 注解式实现
    在指定的方法上面加@Transaction注解

    @Transactional(propagation=Propagation.REQUIRES_NEW,            isolation=Isolation.READ_COMMITTED,            readOnly=false,            timeout=3)    @Overridepublic void purchase(double price, String txId) {}

    由上可知注解式的实现效率不高,我们需要在每一个需要事务代理的方法前加上 @Transaction 注解

  • 配置式实现(Spring 声明式事务)
    结合Spring AOP 实现动态代理横向切入以实现事务控制。

  • 为了在 Spring 2.x 中启用声明式事务管理, 可以通过 tx Schema 中定义的 《tx:advice> 元素声明事务通知, 为此必须事先将这个 Schema 定义添加到 《beans> 根元素中去。

  • 声明了事务通知后, 就需要将它与切入点关联起来. 由于事务通知是在 《aop:config> 元素外部声明的, 所以它无法直接与切入点产生关联. 所以必须在 《aop:config> 元素中声明一个增强器通知与切入点关联起来。

  • 由于 Spring AOP 是基于代理的方法, 所以只能增强公共方法. 因此, 只有公有方法才能通过 Spring AOP 进行事务管理。

  • 一个栗子

    <!-- 配置属性文件 -->    <bean id="propertyConfigurer"        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">        <property name="locations">            <list>                <value>/WEB-INF/classes/dbconfig.properties</value>                <value>/WEB-INF/classes/redis.properties</value>            </list>        </property>    </bean>    <!-- 阿里 druid数据库连接池 -->    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"        destroy-method="close">        <!-- 数据库基本信息配置 -->        <property name="url" value="${url}" />        <property name="username" value="${username}" />        <property name="password" value="${password}" />        <property name="driverClassName" value="${driverClassName}" />        <property name="filters" value="${filters}" />        <!-- 最大并发连接数 -->        <property name="maxActive" value="${maxActive}" />        <!-- 初始化连接数量 -->        <property name="initialSize" value="${initialSize}" />        <!-- 配置获取连接等待超时的时间 -->        <property name="maxWait" value="${maxWait}" />        <!-- 最小空闲连接数 -->        <property name="minIdle" value="${minIdle}" />        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->        <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->        <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />        <property name="validationQuery" value="${validationQuery}" />        <property name="testWhileIdle" value="${testWhileIdle}" />        <property name="testOnBorrow" value="${testOnBorrow}" />        <property name="testOnReturn" value="${testOnReturn}" />        <property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" />        <!-- 打开removeAbandoned功能 -->        <property name="removeAbandoned" value="${removeAbandoned}" />        <!-- 1800秒,也就是30分钟 -->        <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />        <!-- 关闭abanded连接时输出错误日志 -->        <property name="logAbandoned" value="${logAbandoned}" />    </bean>    <!-- 事务配置 -->    <bean name="transactionManager"        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"></property>    </bean>    <tx:advice id="txAdvice" transaction-manager="transactionManager">        <tx:attributes>            <tx:method name="delete*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" isolation="DEFAULT"/>            <tx:method name="insert*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" />            <tx:method name="update*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" />            <tx:method name="save*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" />                <!-- 除以上所有的类型之外 -->            <tx:method name="*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" />        </tx:attributes>    </tx:advice>    <aop:aspectj-autoproxy proxy-target-class="true" />    <!-- 事务处理 -->    <aop:config>        <!-- 指定路劲下的所有方法(可携带N参数)的所有返回值进行事务控制 -->        <!-- * 所有返回值,  com.fh.service..*(..) service下的所有java文件的所有方法可携有无线参数-->        <aop:pointcut id="pc" expression="execution(* com.fh.service..*(..))" />        <!-- 配置事务增强 -->        <aop:advisor pointcut-ref="pc" advice-ref="txAdvice" />    </aop:config>
    • 代码解读:

      Spring 声明式事务只需结合Spring AOP 在配置文件中做出声明即可,无须嵌入代码。

      • 需要而外加入Spring tx jar 包

        <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-tx</artifactId>    <version>${spring.version}</version></dependency>
      • 配置 数据源

            <!-- 配置属性文件 -->    <bean id="propertyConfigurer"        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">        <property name="locations">            <list>                <value>/WEB-INF/classes/dbconfig.properties</value>                <value>/WEB-INF/classes/redis.properties</value>            </list>        </property>    </bean>    <!-- 阿里 druid数据库连接池 -->    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"        destroy-method="close">        <!-- 数据库基本信息配置 -->        <property name="url" value="${url}" />        <property name="username" value="${username}" />        <property name="password" value="${password}" />        <property name="driverClassName" value="${driverClassName}" />        <property name="filters" value="${filters}" />        <!-- 最大并发连接数 -->        <property name="maxActive" value="${maxActive}" />        <!-- 初始化连接数量 -->        <property name="initialSize" value="${initialSize}" />        <!-- 配置获取连接等待超时的时间 -->        <property name="maxWait" value="${maxWait}" />        <!-- 最小空闲连接数 -->        <property name="minIdle" value="${minIdle}" />        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->        <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->        <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />        <property name="validationQuery" value="${validationQuery}" />        <property name="testWhileIdle" value="${testWhileIdle}" />        <property name="testOnBorrow" value="${testOnBorrow}" />        <property name="testOnReturn" value="${testOnReturn}" />        <property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" />        <!-- 打开removeAbandoned功能 -->        <property name="removeAbandoned" value="${removeAbandoned}" />        <!-- 1800秒,也就是30分钟 -->        <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />        <!-- 关闭abanded连接时输出错误日志 -->        <property name="logAbandoned" value="${logAbandoned}" />    </bean>
      • 声明开启事务

        <!-- 事务配置 -->    <bean name="transactionManager"        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"></property>    </bean>
      • 设置事务物属性

        <tx:advice id="txAdvice" transaction-manager="transactionManager">        <tx:attributes>            <tx:method name="delete*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" isolation="DEFAULT"/>            <tx:method name="insert*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" />            <tx:method name="update*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" />            <tx:method name="save*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" />                <!-- 除以上所有的类型之外 -->            <tx:method name="*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" />        </tx:attributes>    </tx:advice>
      • 声明开启动态代理

        <aop:aspectj-autoproxy proxy-target-class="true" />
      • 声明 事务与 AOP 切入点结合

        <!-- 事务处理 -->    <aop:config>        <!-- 指定路劲下的所有方法(可携带N参数)的所有返回值进行事务控制 -->        <!-- * 所有返回值,  com.fh.service..*(..) service下的所有java文件的所有方法可携有无线参数-->        <aop:pointcut id="pc" expression="execution(* com.fh.service..*(..))" />        <!-- 配置事务增强 -->        <aop:advisor pointcut-ref="pc" advice-ref="txAdvice" />    </aop:config>

Spring Tx Properties

我们以声明式事务为例子说明一下 Spring 事务的一些属性和设置。

<tx:method name="delete*" propagation="REQUIRED" read-only="false"                rollback-for="java.lang.Exception" isolation="DEFAULT" timeout="5"/>
  • name:方法名称,delete*标识所有已delete开始的方法

  • propagation:事务传播特性

    • 当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行。
    • 事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为。
      这里写图片描述

    • 下面我们着重了解一下 REQUIRED 和 REQUIRES_NEW 两种传播特性。

      • REQUIRED 传播行为

        • 当 A 方法被另一个事务方法 B 调用时, 它默认会在现有的事务内运行. 这个默认的传播行为就是 REQUIRED. 因此在 B 方法的开始和终止边界内只有一个事务. 这个事务只在 B 方法结束的时候被提交。
          这里写图片描述
      • REQUIRES_NEW 传播行为

        • 另一种常见的传播行为是 REQUIRES_NEW. 它表示该方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它。
          这里写图片描述
    • 并发事务所导致的问题

      • 当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时, 可能会出现许多意外的问题。

      • 并发事务所导致的问题可以分为下面三种类型

        • 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但 还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的。

        • 不可重复读:对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了。

        • 幻读:对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行。

  • read-only & timeOut:事务只读 和事务超时响应

    • 由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响。

    • 如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化。

    • 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源。

    • 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

  • rollback-for:异常返回,如果有不止一种异常, 用逗号分隔

    • 默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会

    • 事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类。

      • rollbackFor: 遇到时必须进行回滚(一般建议异常时强制回滚)

      • noRollbackFor: 一组异常类,遇到时必须不回滚

  • isolation:事务的隔离级别
    这里写图片描述

    • 事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持

    • Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE

    • Mysql 支持 4 中事务隔离级别


阅读全文
0 0
原创粉丝点击