Spring源码剖析8Spring事务概述

41次阅读

共计 17589 个字符,预计需要花费 44 分钟才能阅读完成。

原文出处:张开涛

数据库事务概述

事务首先是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做,这就是事务。

事务必需满足 ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:

  • 原子性(Atomicity):即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做;
  • 一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态,即数据完整性约束没有被破坏;如银行转帐,A 转帐给 B,必须保证 A 的钱一定转给 B,一定不会出现 A 的钱转了但 B 没收到,否则数据库的数据就处于不一致(不正确)的状态。
  • 隔离性(Isolation):并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性;
  • 持久性(Durability):事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失。

在实际项目开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题如下:

  • 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的;
  • 脏读:一个事务看到了另一个事务未提交的更新数据;
  • 不可重复读:在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据;
  • 幻读:一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。

为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准 SQL 规范中定义了四种隔离级别:

  • 未提交读(Read Uncommitted):最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;
  • 提交读(Read Committed):一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不可能可能出现丢失更新、脏读,但可能出现不可重复读、幻读;
  • 可重复读(Repeatable Read):保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,可能可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
  • 序列化(Serializable):最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读。

隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。

事务类型

数据库事务类型有本地事务和分布式事务:

  • 本地事务:就是普通事务,能保证单台数据库上的操作的 ACID,被限定在一台数据库上;
  • 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的 ACID,使事务可以跨越多台数据库;

Java 事务类型有 JDBC 事务和 JTA 事务:

  • JDBC 事务:就是数据库事务类型中的本地事务,通过 Connection 对象的控制来管理事务;
  • JTA 事务:JTA 指 Java 事务 API(Java Transaction API),是 Java EE 数据库事务规范,JTA 只提供了事务管理接口,由应用程序服务器厂商(如 WebSphere Application Server)提供实现,JTA 事务比 JDBC 更强大,支持分布式事务。

Java EE 事务类型有本地事务和全局事务:

  • 本地事务:使用 JDBC 编程实现事务;
  • 全局事务:由应用程序服务器提供,使用 JTA 事务;

按是否通过编程实现事务有声明式事务和编程式事务;

  • 声明式事务:通过注解或 XML 配置文件指定事务信息;
  • 编程式事务:通过编写代码实现事务。

Spring 提供的事务管理

Spring 框架最核心功能之一就是事务管理,而且提供一致的事务管理抽象,这能帮助我们:

  • 提供一致的编程式事务管理 API,不管使用 Spring JDBC 框架还是集成第三方框架使用该 API 进行事务编程;
  • 无侵入式的声明式事务支持。

Spring 支持声明式事务和编程式事务事务类型。

spring 事务特性

spring 所有的事务管理策略类都继承自 org.springframework.transaction.PlatformTransactionManager 接口

其中 TransactionDefinition 接口定义以下特性:

事务隔离级别

  隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是 TransactionDefinition.ISOLATION_READ_COMMITTED。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如 PostgreSQL 实际上并没有此级别。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为

      所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。

事务超时

      所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

  默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是 none,没有超时限制。

事务只读属性

      只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用 Hibernate 的时候。

默认为读写事务。

概述

Spring 框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架(如 Hibernate)通过实现策略接口 PlatformTransactionManager,从而能支持各种数据访问框架的事务管理,PlatformTransactionManager 接口定义如下:

java 代码:


public interface PlatformTransactionManager {TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
       void commit(TransactionStatus status) throws TransactionException;
       void rollback(TransactionStatus status) throws TransactionException;
}






  • getTransaction():返回一个已经激活的事务或创建一个新的事务(根据给定的 TransactionDefinition 类型参数定义的事务属性),返回的是 TransactionStatus 对象代表了当前事务的状态,其中该方法抛出 TransactionException(未检查异常)表示事务由于某种原因失败。
  • commit():用于提交 TransactionStatus 参数代表的事务,具体语义请参考 Spring Javadoc;
  • rollback():用于回滚 TransactionStatus 参数代表的事务,具体语义请参考 Spring Javadoc。

TransactionDefinition 接口定义如下:

java 代码:


public interface TransactionDefinition {int getPropagationBehavior();
       int getIsolationLevel();
       int getTimeout();
       boolean isReadOnly();
       String getName();}







  • getPropagationBehavior():返回定义的事务传播行为;
  • getIsolationLevel():返回定义的事务隔离级别;
  • getTimeout():返回定义的事务超时时间;
  • isReadOnly():返回定义的事务是否是只读的;
  • getName():返回定义的事务名字。

TransactionStatus 接口定义如下:

java 代码:

public interface TransactionStatus extends SavepointManager {boolean isNewTransaction();
       boolean hasSavepoint();
       void setRollbackOnly();
       boolean isRollbackOnly();
       void flush();
       boolean isCompleted();}




  • isNewTransaction():返回当前事务状态是否是新事务;
  • hasSavepoint():返回当前事务是否有保存点;
  • setRollbackOnly():设置当前事务应该回滚;
  • isRollbackOnly(():返回当前事务是否应该回滚;
  • flush():用于刷新底层会话中的修改到数据库,一般用于刷新如 Hibernate/JPA 的会话,可能对如 JDBC 类型的事务无任何影响;
  • isCompleted(): 当前事务否已经完成。

内置事务管理器实现

Spring 提供了许多内置事务管理器实现:

  • DataSourceTransactionManager:位于 org.springframework.jdbc.datasource 包中,数据源事务管理器,提供对单个 javax.sql.DataSource 事务管理,用于 Spring JDBC 抽象框架、iBATIS 或 MyBatis 框架的事务管理;
  • JdoTransactionManager:位于 org.springframework.orm.jdo 包中,提供对单个 javax.jdo.PersistenceManagerFactory 事务管理,用于集成 JDO 框架时的事务管理;
  • JpaTransactionManager:位于 org.springframework.orm.jpa 包中,提供对单个 javax.persistence.EntityManagerFactory 事务支持,用于集成 JPA 实现框架时的事务管理;
  • HibernateTransactionManager:位于 org.springframework.orm.hibernate3 包中,提供对单个 org.hibernate.SessionFactory 事务支持,用于集成 Hibernate 框架时的事务管理;该事务管理器只支持 Hibernate3+ 版本,且 Spring3.0+ 版本只支持 Hibernate 3.2+ 版本;
  • JtaTransactionManager:位于 org.springframework.transaction.jta 包中,提供对分布式事务管理的支持,并将事务管理委托给 Java EE 应用服务器事务管理器;
  • OC4JjtaTransactionManager:位于 org.springframework.transaction.jta 包中,Spring 提供的对 OC4J10.1.3+ 应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
  • WebSphereUowTransactionManager:位于 org.springframework.transaction.jta 包中,Spring 提供的对 WebSphere 6.0+ 应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
  • WebLogicJtaTransactionManager:位于 org.springframework.transaction.jta 包中,Spring 提供的对 WebLogic 8.1+ 应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。

Spring 不仅提供这些事务管理器,还提供对如 JMS 事务管理的管理器等,Spring 提供一致的事务抽象如图 9 - 1 所示。

图 9 -1 Spring 事务管理器

接下来让我们学习一下如何在 Spring 配置文件中定义事务管理器:

一、声明对本地事务的支持:

a)JDBC 及 iBATIS、MyBatis 框架事务管理器

java 代码:


<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>








通过 dataSource 属性指定需要事务管理的单个 javax.sql.DataSource 对象。

b)Jdo 事务管理器

java 代码:


<bean id="txManager" class="org.springframework.orm.jdo.JdoTransactionManager">
    <property name="persistenceManagerFactory" ref="persistenceManagerFactory"/>
</bean>








通过 persistenceManagerFactory 属性指定需要事务管理的 javax.jdo.PersistenceManagerFactory 对象。

c)Jpa 事务管理器

java 代码:

bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>







通过 entityManagerFactory 属性指定需要事务管理的 javax.persistence.EntityManagerFactory 对象。

还需要为 entityManagerFactory 对象指定 jpaDialect 属性,该属性所对应的对象指定了如何获取连接对象、开启事务、关闭事务等事务管理相关的行为。

java 代码:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        ……
        <property name="jpaDialect" ref="jpaDialect"/>
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>








d)Hibernate 事务管理器

java 代码:

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>






通过 entityManagerFactory 属性指定需要事务管理的 org.hibernate.SessionFactory 对象。

声明式事务

声明式事务概述

从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量。

本节将介绍声明式事务支持,使用该方式后最大的获益是简单,事务管理不再是令人痛苦的,而且此方式属于无侵入式,对业务逻辑实现无影响。

接下来先来看看声明式事务如何实现吧。

声明式实现事务管理

1、定义业务逻辑实现,此处使用 ConfigUserServiceImpl 和 ConfigAddressServiceImpl:

2、定义配置文件(chapter9/service/ applicationContext-service-declare.xml):

2.1、XML 命名空间定义,定义用于事务支持的 tx 命名空间和 AOP 支持的 aop 命名空间:

java 代码:<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:aop="http://www.springframework.org/schema/aop"
      xsi:schemaLocation="
 
http://www.springframework.org/schema/beans
 
 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 
 
http://www.springframework.org/schema/tx
 
 
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
 
 
http://www.springframework.org/schema/aop
 
 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:

java 代码:<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="addressService" ref="addressService"/>
</bean>
<bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
    <property name="addressDao" ref="addressDao"/>
</bean>

2.3、事务相关配置:

java 代码:<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
        <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/>
    </tx:attributes>
</tx:advice>

java 代码:


<aop:config>
    <aop:pointcut id="serviceMethod" expression="execution(* cn..chapter9.service..*.*(..))"/>
    <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>
</aop:config>
<tx:advice>:事务通知定义,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过 < tx:attributes > 指定具体需要拦截的方法;<tx:method name=”save*”>:表示将拦截以 save 开头的方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是 Required,isolation=”READ_COMMITTED”表示隔离级别是提交读;<tx:method name=”*”>:表示将拦截其他所有方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是 Required,isolation=”READ_COMMITTED”表示隔离级别是提交读,read-only=”true”表示事务只读;<aop:config>:AOP 相关配置:<aop:pointcut/>:切入点定义,定义名为”serviceMethod”的 aspectj 切入点,切入点表达式为”execution(* cn..chapter9.service..*.*(..))”表示拦截 cn 包及子包下的 chapter9. service 包及子包下的任何类的任何方法;<aop:advisor>:Advisor 定义,其中切入点为 serviceMethod,通知为 txAdvice。从配置中可以看出,将对 cn 包及子包下的 chapter9. service 包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。

3、修改测试方法并测试该配置方式是否好用:

将 TransactionTest 类的 testServiceTransaction 测试方法拷贝一份命名为 testDeclareTransaction:

并在 testDeclareTransaction 测试方法内将:

4、执行测试,测试正常通过,说明该方式能正常工作,当调用 save 方法时将匹配到事务通知中定义的“<tx:method name=”save”>”中指定的事务属性,而调用 countAll 方法时将匹配到事务通知中定义的“<tx:method name=””>”中指定的事务属性。

声明式事务是如何实现事务管理的呢?还记不记得 TransactionProxyFactoryBean 实现配置式事务管理,配置式事务管理是通过代理方式实现,而声明式事务管理同样是通过 AOP 代理方式实现。

声明式事务通过 AOP 代理方式实现事务管理,利用环绕通知 TransactionInterceptor 实现事务的开启及关闭,而 TransactionProxyFactoryBean 内部也是通过该环绕通知实现的,因此可以认为是 <tx:tags/> 帮你定义了 TransactionProxyFactoryBean,从而简化事务管理。

了解了实现方式后,接下来详细学习一下配置吧:

9.4.4 <tx:advice/> 配置详解
声明式事务管理通过配置 <tx:advice/> 来定义事务属性,配置方式如下所示:

java 代码:<tx:advice id="……" transaction-manager="……">
<tx:attributes>
        <tx:method name="……"
                           propagation="REQUIRED"
                           isolation="READ_COMMITTED"
                           timeout="-1"
                           read-only="false"
                           no-rollback-for=""rollback-for=""/>
        ……
    </tx:attributes>
</tx:advice>
<tx:advice>:id 用于指定此通知的名字,transaction-manager 用于指定事务管理器,默认的事务管理器名字为“transactionManager”;<tx:method>:用于定义事务属性即相关联的方法名;

name:定义与事务属性相关联的方法名,将对匹配的方法应用定义的事务属性,可以使用“”通配符来匹配一组或所有方法,如“save”将匹配以 save 开头的方法,而“*”将匹配所有方法;

propagation:事务传播行为定义,默认为“REQUIRED”,表示 Required,其值可以通过 TransactionDefinition 的静态传播行为变量的“PROPAGATION_”后边部分指定,如“TransactionDefinition.PROPAGATION_REQUIRED”可以使用“REQUIRED”指定;

isolation:事务隔离级别定义;默认为“DEFAULT”,其值可以通过 TransactionDefinition 的静态隔离级别变量的“ISOLATION_”后边部分指定,如“TransactionDefinition. ISOLATION_DEFAULT”可以使用“DEFAULT”指定:

timeout:事务超时时间设置,单位为秒,默认 -1,表示事务超时将依赖于底层事务系统;

read-only:事务只读设置,默认为 false,表示不是只读;

rollback-for:需要触发回滚的异常定义,以“,”分割,默认任何 RuntimeException 将导致事务回滚,而任何 Checked Exception 将不导致事务回滚;异常名字定义和 TransactionProxyFactoryBean 中含义一样

no-rollback-for:不被触发进行回滚的 Exception(s);以“,”分割;异常名字定义和 TransactionProxyFactoryBean 中含义一样;

记不记得在配置方式中为了解决“自我调用”而导致的不能设置正确的事务属性问题,使用“((IUserService)AopContext.currentProxy()).otherTransactionMethod()”方式解决,在声明式事务要得到支持需要使用 <aop:config expose-proxy=”true”> 来开启。

9.4.5 多事务语义配置及最佳实践
什么是多事务语义?说白了就是为不同的 Bean 配置不同的事务属性,因为我们项目中不可能就几个 Bean,而可能很多,这可能需要为 Bean 分组,为不同组的 Bean 配置不同的事务语义。在 Spring 中,可以通过配置多切入点和多事务通知并通过不同方式组合使用即可。

   1、首先看下声明式事务配置的最佳实践吧:<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
           <tx:method name="save*" propagation="REQUIRED" />
           <tx:method name="add*" propagation="REQUIRED" />
           <tx:method name="create*" propagation="REQUIRED" />
           <tx:method name="insert*" propagation="REQUIRED" />
           <tx:method name="update*" propagation="REQUIRED" />
           <tx:method name="merge*" propagation="REQUIRED" />
           <tx:method name="del*" propagation="REQUIRED" />
           <tx:method name="remove*" propagation="REQUIRED" />
           <tx:method name="put*" propagation="REQUIRED" />
           <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
           <tx:method name="count*" propagation="SUPPORTS" read-only="true" />
          <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
          <tx:method name="list*" propagation="SUPPORTS" read-only="true" />
          <tx:method name="*" propagation="SUPPORTS" read-only="true" />
       </tx:attributes>
</tx:advice>
<aop:config>
       <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />
       <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>

该声明式事务配置可以应付常见的 CRUD 接口定义,并实现事务管理,我们只需修改切入点表达式来拦截我们的业务实现从而对其应用事务属性就可以了,如果还有更复杂的事务属性直接添加即可,即

如果我们有一个 batchSaveOrUpdate 方法需要“REQUIRES_NEW”事务传播行为,则直接添加如下配置即可:

java 代码:
1
<tx:method name=”batchSaveOrUpdate” propagation=”REQUIRES_NEW” />
2、接下来看一下多事务语义配置吧,声明式事务最佳实践中已经配置了通用事务属性,因此可以针对需要其他事务属性的业务方法进行特例化配置:

java 代码:<tx:advice id="noTxAdvice" transaction-manager="txManager">
    <tx:attributes>
           <tx:method name="*" propagation="NEVER" />
    </tx:attributes>
</tx:advice>
<aop:config>
       <aop:pointcut id="noTxPointcut" expression="execution(* cn.javass..util.*.*())" />
       <aop:advisor advice-ref="noTxPointcut" pointcut-ref="noTxAdvice" />
</aop:config>

该声明将对切入点匹配的方法所在事务应用“Never”传播行为。

多事务语义配置时,切入点一定不要叠加,否则将应用两次事务属性,造成不必要的错误及麻烦。

@Transactional 实现事务管理

对声明式事务管理,Spring 提供基于 @Transactional 注解方式来实现,但需要 Java 5+。

注解方式是最简单的事务配置方式,可以直接在 Java 源代码中声明事务属性,且对于每一个业务类或方法如果需要事务都必须使用此注解。

接下来学习一下注解事务的使用吧:

1、定义业务逻辑实现:

package cn.javass.spring.chapter9.service.impl;
// 省略 import
public class AnnotationUserServiceImpl implements IUserService {
    private IUserDao userDao;
    private IAddressService addressService;
    public void setUserDao(IUserDao userDao) {this.userDao = userDao;}
    public void setAddressService(IAddressService addressService) {this.addressService = addressService;}
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
    @Override
    public void save(final UserModel user) {userDao.save(user);
        user.getAddress().setUserId(user.getId());
        addressService.save(user.getAddress());
    }
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true)
    @Override
    public int countAll() {return userDao.countAll();
    }
}

2、定义配置文件(chapter9/service/ applicationContext-service-annotation.xml):

2.1、XML 命名空间定义,定义用于事务支持的 tx 命名空间和 AOP 支持的 aop 命名空间:

java 代码:

    <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:tx="http://www.springframework.org/schema/tx"
          xmlns:aop="http://www.springframework.org/schema/aop"
          xsi:schemaLocation="
     
    http://www.springframework.org/schema/beans
     
     
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     
     
    http://www.springframework.org/schema/tx
     
     
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
     
     
    http://www.springframework.org/schema/aop
     
     
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:

java 代码:<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="addressService" ref="addressService"/>
</bean>
<bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
    <property name="addressDao" ref="addressDao"/>
</bean>

2.3、事务相关配置:


java 代码:1
<tx:annotation-driven transaction-manager="txManager"/>
使用如上配置已支持声明式事务。3、修改测试方法并测试该配置方式是否好用:将 TransactionTest 类的 testServiceTransaction 测试方法拷贝一份命名为 testAnntationTransactionTest:classpath:chapter9/service/applicationContext-service-annotation.xml"

userService.save(user);
try {userService.save(user);
    Assert.fail();} catch (RuntimeException e) {
}
Assert.assertEquals(0, userService.countAll());
Assert.assertEquals(0, addressService.countAll());

4、执行测试,测试正常通过,说明该方式能正常工作,因为在 AnnotationAddressServiceImpl 类的 save 方法中抛出异常,因此事务需要回滚,所以两个 countAll 操作都返回 0。

9.4.7 @Transactional 配置详解
Spring 提供的 <tx:annotation-driven/> 用于开启对注解事务管理的支持,从而能识别 Bean 类上的 @Transactional 注解元数据,其具有以下属性:

transaction-manager:指定事务管理器名字,默认为 transactionManager,当使用其他名字时需要明确指定;
proxy-target-class:表示将使用的代码机制,默认 false 表示使用 JDK 代理,如果为 true 将使用 CGLIB 代理
order:定义事务通知顺序,默认 Ordered.LOWEST_PRECEDENCE,表示将顺序决定权交给 AOP 来处理。
Spring 使用 @Transactional 来指定事务属性,可以在接口、类或方法上指定,如果类和方法上都指定了 @Transactional,则方法上的事务属性被优先使用,具体属性如下:

value:指定事务管理器名字,默认使用 <tx:annotation-driven/> 指定的事务管理器,用于支持多事务管理器环境;
propagation:指定事务传播行为,默认为 Required,使用 Propagation.REQUIRED 指定;
isolation:指定事务隔离级别,默认为“DEFAULT”,使用 Isolation.DEFAULT 指定;
readOnly:指定事务是否只读,默认 false 表示事务非只读;
timeout:指定事务超时时间,以秒为单位,默认 - 1 表示事务超时将依赖于底层事务系统;
rollbackFor:指定一组异常类,遇到该类异常将回滚事务;
rollbackForClassname:指定一组异常类名字,其含义与 <tx:method> 中的 rollback-for 属性语义完全一样;
noRollbackFor:指定一组异常类,即使遇到该类异常也将提交事务,即不回滚事务;
noRollbackForClassname:指定一组异常类名字,其含义与 <tx:method> 中的 no-rollback-for 属性语义完全一样;
Spring 提供的 @Transactional 注解事务管理内部同样利用环绕通知 TransactionInterceptor 实现事务的开启及关闭。

使用 @Transactional 注解事务管理需要特别注意以下几点:

如果在接口、实现类或方法上都指定了 @Transactional 注解,则优先级顺序为方法 > 实现类 > 接口;
建议只在实现类或实现类的方法上使用 @Transactional,而不要在接口上使用,这是因为如果使用 JDK 代理机制是没问题,因为其使用基于接口的代理;而使用使用 CGLIB 代理机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的 @Transactional 注解是“不能继承的”;

              具体请参考基于 JDK 动态代理和 CGLIB 动态代理的实现 Spring 注解管理事务(@Trasactional)到底有什么区别。

在 Spring 代理机制下 (不管是 JDK 动态代理还是 CGLIB 代理),“自我调用”同样不会应用相应的事务属性,其语义和 <tx:tags> 中一样;
默认只对 RuntimeException 异常回滚;
在使用 Spring 代理时,默认只有在 public 可见度的方法的 @Transactional 注解才是有效的,其它可见度(protected、private、包可见)的方法上即使有 @Transactional 注解也不会应用这些事务属性的,Spring 也不会报错,如果你非要使用非公共方法注解事务管理的话,可考虑使用 AspectJ。

微信公众号【黄小斜】作者是蚂蚁金服 JAVA 工程师,专注于 JAVA
后端技术栈:SpringBoot、SSM 全家桶、MySQL、分布式、中间件、微服务,同时也懂点投资理财,坚持学习和写作,相信终身学习的力量!关注公众号后回复”架构师“即可领取
Java 基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的 Java 学习指南、Java 程序员面试指南等干货资源

正文完
 0

Spring源码剖析8Spring事务概述

44次阅读

共计 17589 个字符,预计需要花费 44 分钟才能阅读完成。

原文出处:张开涛

数据库事务概述

事务首先是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做,这就是事务。

事务必需满足 ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:

  • 原子性(Atomicity):即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做;
  • 一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态,即数据完整性约束没有被破坏;如银行转帐,A 转帐给 B,必须保证 A 的钱一定转给 B,一定不会出现 A 的钱转了但 B 没收到,否则数据库的数据就处于不一致(不正确)的状态。
  • 隔离性(Isolation):并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性;
  • 持久性(Durability):事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失。

在实际项目开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题如下:

  • 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的;
  • 脏读:一个事务看到了另一个事务未提交的更新数据;
  • 不可重复读:在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据;
  • 幻读:一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。

为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准 SQL 规范中定义了四种隔离级别:

  • 未提交读(Read Uncommitted):最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;
  • 提交读(Read Committed):一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不可能可能出现丢失更新、脏读,但可能出现不可重复读、幻读;
  • 可重复读(Repeatable Read):保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,可能可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
  • 序列化(Serializable):最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读。

隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。

事务类型

数据库事务类型有本地事务和分布式事务:

  • 本地事务:就是普通事务,能保证单台数据库上的操作的 ACID,被限定在一台数据库上;
  • 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的 ACID,使事务可以跨越多台数据库;

Java 事务类型有 JDBC 事务和 JTA 事务:

  • JDBC 事务:就是数据库事务类型中的本地事务,通过 Connection 对象的控制来管理事务;
  • JTA 事务:JTA 指 Java 事务 API(Java Transaction API),是 Java EE 数据库事务规范,JTA 只提供了事务管理接口,由应用程序服务器厂商(如 WebSphere Application Server)提供实现,JTA 事务比 JDBC 更强大,支持分布式事务。

Java EE 事务类型有本地事务和全局事务:

  • 本地事务:使用 JDBC 编程实现事务;
  • 全局事务:由应用程序服务器提供,使用 JTA 事务;

按是否通过编程实现事务有声明式事务和编程式事务;

  • 声明式事务:通过注解或 XML 配置文件指定事务信息;
  • 编程式事务:通过编写代码实现事务。

Spring 提供的事务管理

Spring 框架最核心功能之一就是事务管理,而且提供一致的事务管理抽象,这能帮助我们:

  • 提供一致的编程式事务管理 API,不管使用 Spring JDBC 框架还是集成第三方框架使用该 API 进行事务编程;
  • 无侵入式的声明式事务支持。

Spring 支持声明式事务和编程式事务事务类型。

spring 事务特性

spring 所有的事务管理策略类都继承自 org.springframework.transaction.PlatformTransactionManager 接口

其中 TransactionDefinition 接口定义以下特性:

事务隔离级别

  隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是 TransactionDefinition.ISOLATION_READ_COMMITTED。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如 PostgreSQL 实际上并没有此级别。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为

      所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。

事务超时

      所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

  默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是 none,没有超时限制。

事务只读属性

      只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用 Hibernate 的时候。

默认为读写事务。

概述

Spring 框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架(如 Hibernate)通过实现策略接口 PlatformTransactionManager,从而能支持各种数据访问框架的事务管理,PlatformTransactionManager 接口定义如下:

java 代码:


public interface PlatformTransactionManager {TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
       void commit(TransactionStatus status) throws TransactionException;
       void rollback(TransactionStatus status) throws TransactionException;
}






  • getTransaction():返回一个已经激活的事务或创建一个新的事务(根据给定的 TransactionDefinition 类型参数定义的事务属性),返回的是 TransactionStatus 对象代表了当前事务的状态,其中该方法抛出 TransactionException(未检查异常)表示事务由于某种原因失败。
  • commit():用于提交 TransactionStatus 参数代表的事务,具体语义请参考 Spring Javadoc;
  • rollback():用于回滚 TransactionStatus 参数代表的事务,具体语义请参考 Spring Javadoc。

TransactionDefinition 接口定义如下:

java 代码:


public interface TransactionDefinition {int getPropagationBehavior();
       int getIsolationLevel();
       int getTimeout();
       boolean isReadOnly();
       String getName();}







  • getPropagationBehavior():返回定义的事务传播行为;
  • getIsolationLevel():返回定义的事务隔离级别;
  • getTimeout():返回定义的事务超时时间;
  • isReadOnly():返回定义的事务是否是只读的;
  • getName():返回定义的事务名字。

TransactionStatus 接口定义如下:

java 代码:

public interface TransactionStatus extends SavepointManager {boolean isNewTransaction();
       boolean hasSavepoint();
       void setRollbackOnly();
       boolean isRollbackOnly();
       void flush();
       boolean isCompleted();}




  • isNewTransaction():返回当前事务状态是否是新事务;
  • hasSavepoint():返回当前事务是否有保存点;
  • setRollbackOnly():设置当前事务应该回滚;
  • isRollbackOnly(():返回当前事务是否应该回滚;
  • flush():用于刷新底层会话中的修改到数据库,一般用于刷新如 Hibernate/JPA 的会话,可能对如 JDBC 类型的事务无任何影响;
  • isCompleted(): 当前事务否已经完成。

内置事务管理器实现

Spring 提供了许多内置事务管理器实现:

  • DataSourceTransactionManager:位于 org.springframework.jdbc.datasource 包中,数据源事务管理器,提供对单个 javax.sql.DataSource 事务管理,用于 Spring JDBC 抽象框架、iBATIS 或 MyBatis 框架的事务管理;
  • JdoTransactionManager:位于 org.springframework.orm.jdo 包中,提供对单个 javax.jdo.PersistenceManagerFactory 事务管理,用于集成 JDO 框架时的事务管理;
  • JpaTransactionManager:位于 org.springframework.orm.jpa 包中,提供对单个 javax.persistence.EntityManagerFactory 事务支持,用于集成 JPA 实现框架时的事务管理;
  • HibernateTransactionManager:位于 org.springframework.orm.hibernate3 包中,提供对单个 org.hibernate.SessionFactory 事务支持,用于集成 Hibernate 框架时的事务管理;该事务管理器只支持 Hibernate3+ 版本,且 Spring3.0+ 版本只支持 Hibernate 3.2+ 版本;
  • JtaTransactionManager:位于 org.springframework.transaction.jta 包中,提供对分布式事务管理的支持,并将事务管理委托给 Java EE 应用服务器事务管理器;
  • OC4JjtaTransactionManager:位于 org.springframework.transaction.jta 包中,Spring 提供的对 OC4J10.1.3+ 应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
  • WebSphereUowTransactionManager:位于 org.springframework.transaction.jta 包中,Spring 提供的对 WebSphere 6.0+ 应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
  • WebLogicJtaTransactionManager:位于 org.springframework.transaction.jta 包中,Spring 提供的对 WebLogic 8.1+ 应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。

Spring 不仅提供这些事务管理器,还提供对如 JMS 事务管理的管理器等,Spring 提供一致的事务抽象如图 9 - 1 所示。

图 9 -1 Spring 事务管理器

接下来让我们学习一下如何在 Spring 配置文件中定义事务管理器:

一、声明对本地事务的支持:

a)JDBC 及 iBATIS、MyBatis 框架事务管理器

java 代码:


<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>








通过 dataSource 属性指定需要事务管理的单个 javax.sql.DataSource 对象。

b)Jdo 事务管理器

java 代码:


<bean id="txManager" class="org.springframework.orm.jdo.JdoTransactionManager">
    <property name="persistenceManagerFactory" ref="persistenceManagerFactory"/>
</bean>








通过 persistenceManagerFactory 属性指定需要事务管理的 javax.jdo.PersistenceManagerFactory 对象。

c)Jpa 事务管理器

java 代码:

bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>







通过 entityManagerFactory 属性指定需要事务管理的 javax.persistence.EntityManagerFactory 对象。

还需要为 entityManagerFactory 对象指定 jpaDialect 属性,该属性所对应的对象指定了如何获取连接对象、开启事务、关闭事务等事务管理相关的行为。

java 代码:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        ……
        <property name="jpaDialect" ref="jpaDialect"/>
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>








d)Hibernate 事务管理器

java 代码:

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>






通过 entityManagerFactory 属性指定需要事务管理的 org.hibernate.SessionFactory 对象。

声明式事务

声明式事务概述

从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量。

本节将介绍声明式事务支持,使用该方式后最大的获益是简单,事务管理不再是令人痛苦的,而且此方式属于无侵入式,对业务逻辑实现无影响。

接下来先来看看声明式事务如何实现吧。

声明式实现事务管理

1、定义业务逻辑实现,此处使用 ConfigUserServiceImpl 和 ConfigAddressServiceImpl:

2、定义配置文件(chapter9/service/ applicationContext-service-declare.xml):

2.1、XML 命名空间定义,定义用于事务支持的 tx 命名空间和 AOP 支持的 aop 命名空间:

java 代码:<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:aop="http://www.springframework.org/schema/aop"
      xsi:schemaLocation="
 
http://www.springframework.org/schema/beans
 
 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 
 
http://www.springframework.org/schema/tx
 
 
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
 
 
http://www.springframework.org/schema/aop
 
 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:

java 代码:<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="addressService" ref="addressService"/>
</bean>
<bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
    <property name="addressDao" ref="addressDao"/>
</bean>

2.3、事务相关配置:

java 代码:<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
        <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/>
    </tx:attributes>
</tx:advice>

java 代码:


<aop:config>
    <aop:pointcut id="serviceMethod" expression="execution(* cn..chapter9.service..*.*(..))"/>
    <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>
</aop:config>
<tx:advice>:事务通知定义,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过 < tx:attributes > 指定具体需要拦截的方法;<tx:method name=”save*”>:表示将拦截以 save 开头的方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是 Required,isolation=”READ_COMMITTED”表示隔离级别是提交读;<tx:method name=”*”>:表示将拦截其他所有方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是 Required,isolation=”READ_COMMITTED”表示隔离级别是提交读,read-only=”true”表示事务只读;<aop:config>:AOP 相关配置:<aop:pointcut/>:切入点定义,定义名为”serviceMethod”的 aspectj 切入点,切入点表达式为”execution(* cn..chapter9.service..*.*(..))”表示拦截 cn 包及子包下的 chapter9. service 包及子包下的任何类的任何方法;<aop:advisor>:Advisor 定义,其中切入点为 serviceMethod,通知为 txAdvice。从配置中可以看出,将对 cn 包及子包下的 chapter9. service 包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。

3、修改测试方法并测试该配置方式是否好用:

将 TransactionTest 类的 testServiceTransaction 测试方法拷贝一份命名为 testDeclareTransaction:

并在 testDeclareTransaction 测试方法内将:

4、执行测试,测试正常通过,说明该方式能正常工作,当调用 save 方法时将匹配到事务通知中定义的“<tx:method name=”save”>”中指定的事务属性,而调用 countAll 方法时将匹配到事务通知中定义的“<tx:method name=””>”中指定的事务属性。

声明式事务是如何实现事务管理的呢?还记不记得 TransactionProxyFactoryBean 实现配置式事务管理,配置式事务管理是通过代理方式实现,而声明式事务管理同样是通过 AOP 代理方式实现。

声明式事务通过 AOP 代理方式实现事务管理,利用环绕通知 TransactionInterceptor 实现事务的开启及关闭,而 TransactionProxyFactoryBean 内部也是通过该环绕通知实现的,因此可以认为是 <tx:tags/> 帮你定义了 TransactionProxyFactoryBean,从而简化事务管理。

了解了实现方式后,接下来详细学习一下配置吧:

9.4.4 <tx:advice/> 配置详解
声明式事务管理通过配置 <tx:advice/> 来定义事务属性,配置方式如下所示:

java 代码:<tx:advice id="……" transaction-manager="……">
<tx:attributes>
        <tx:method name="……"
                           propagation="REQUIRED"
                           isolation="READ_COMMITTED"
                           timeout="-1"
                           read-only="false"
                           no-rollback-for=""rollback-for=""/>
        ……
    </tx:attributes>
</tx:advice>
<tx:advice>:id 用于指定此通知的名字,transaction-manager 用于指定事务管理器,默认的事务管理器名字为“transactionManager”;<tx:method>:用于定义事务属性即相关联的方法名;

name:定义与事务属性相关联的方法名,将对匹配的方法应用定义的事务属性,可以使用“”通配符来匹配一组或所有方法,如“save”将匹配以 save 开头的方法,而“*”将匹配所有方法;

propagation:事务传播行为定义,默认为“REQUIRED”,表示 Required,其值可以通过 TransactionDefinition 的静态传播行为变量的“PROPAGATION_”后边部分指定,如“TransactionDefinition.PROPAGATION_REQUIRED”可以使用“REQUIRED”指定;

isolation:事务隔离级别定义;默认为“DEFAULT”,其值可以通过 TransactionDefinition 的静态隔离级别变量的“ISOLATION_”后边部分指定,如“TransactionDefinition. ISOLATION_DEFAULT”可以使用“DEFAULT”指定:

timeout:事务超时时间设置,单位为秒,默认 -1,表示事务超时将依赖于底层事务系统;

read-only:事务只读设置,默认为 false,表示不是只读;

rollback-for:需要触发回滚的异常定义,以“,”分割,默认任何 RuntimeException 将导致事务回滚,而任何 Checked Exception 将不导致事务回滚;异常名字定义和 TransactionProxyFactoryBean 中含义一样

no-rollback-for:不被触发进行回滚的 Exception(s);以“,”分割;异常名字定义和 TransactionProxyFactoryBean 中含义一样;

记不记得在配置方式中为了解决“自我调用”而导致的不能设置正确的事务属性问题,使用“((IUserService)AopContext.currentProxy()).otherTransactionMethod()”方式解决,在声明式事务要得到支持需要使用 <aop:config expose-proxy=”true”> 来开启。

9.4.5 多事务语义配置及最佳实践
什么是多事务语义?说白了就是为不同的 Bean 配置不同的事务属性,因为我们项目中不可能就几个 Bean,而可能很多,这可能需要为 Bean 分组,为不同组的 Bean 配置不同的事务语义。在 Spring 中,可以通过配置多切入点和多事务通知并通过不同方式组合使用即可。

   1、首先看下声明式事务配置的最佳实践吧:<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
           <tx:method name="save*" propagation="REQUIRED" />
           <tx:method name="add*" propagation="REQUIRED" />
           <tx:method name="create*" propagation="REQUIRED" />
           <tx:method name="insert*" propagation="REQUIRED" />
           <tx:method name="update*" propagation="REQUIRED" />
           <tx:method name="merge*" propagation="REQUIRED" />
           <tx:method name="del*" propagation="REQUIRED" />
           <tx:method name="remove*" propagation="REQUIRED" />
           <tx:method name="put*" propagation="REQUIRED" />
           <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
           <tx:method name="count*" propagation="SUPPORTS" read-only="true" />
          <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
          <tx:method name="list*" propagation="SUPPORTS" read-only="true" />
          <tx:method name="*" propagation="SUPPORTS" read-only="true" />
       </tx:attributes>
</tx:advice>
<aop:config>
       <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />
       <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>

该声明式事务配置可以应付常见的 CRUD 接口定义,并实现事务管理,我们只需修改切入点表达式来拦截我们的业务实现从而对其应用事务属性就可以了,如果还有更复杂的事务属性直接添加即可,即

如果我们有一个 batchSaveOrUpdate 方法需要“REQUIRES_NEW”事务传播行为,则直接添加如下配置即可:

java 代码:
1
<tx:method name=”batchSaveOrUpdate” propagation=”REQUIRES_NEW” />
2、接下来看一下多事务语义配置吧,声明式事务最佳实践中已经配置了通用事务属性,因此可以针对需要其他事务属性的业务方法进行特例化配置:

java 代码:<tx:advice id="noTxAdvice" transaction-manager="txManager">
    <tx:attributes>
           <tx:method name="*" propagation="NEVER" />
    </tx:attributes>
</tx:advice>
<aop:config>
       <aop:pointcut id="noTxPointcut" expression="execution(* cn.javass..util.*.*())" />
       <aop:advisor advice-ref="noTxPointcut" pointcut-ref="noTxAdvice" />
</aop:config>

该声明将对切入点匹配的方法所在事务应用“Never”传播行为。

多事务语义配置时,切入点一定不要叠加,否则将应用两次事务属性,造成不必要的错误及麻烦。

@Transactional 实现事务管理

对声明式事务管理,Spring 提供基于 @Transactional 注解方式来实现,但需要 Java 5+。

注解方式是最简单的事务配置方式,可以直接在 Java 源代码中声明事务属性,且对于每一个业务类或方法如果需要事务都必须使用此注解。

接下来学习一下注解事务的使用吧:

1、定义业务逻辑实现:

package cn.javass.spring.chapter9.service.impl;
// 省略 import
public class AnnotationUserServiceImpl implements IUserService {
    private IUserDao userDao;
    private IAddressService addressService;
    public void setUserDao(IUserDao userDao) {this.userDao = userDao;}
    public void setAddressService(IAddressService addressService) {this.addressService = addressService;}
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
    @Override
    public void save(final UserModel user) {userDao.save(user);
        user.getAddress().setUserId(user.getId());
        addressService.save(user.getAddress());
    }
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true)
    @Override
    public int countAll() {return userDao.countAll();
    }
}

2、定义配置文件(chapter9/service/ applicationContext-service-annotation.xml):

2.1、XML 命名空间定义,定义用于事务支持的 tx 命名空间和 AOP 支持的 aop 命名空间:

java 代码:

    <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:tx="http://www.springframework.org/schema/tx"
          xmlns:aop="http://www.springframework.org/schema/aop"
          xsi:schemaLocation="
     
    http://www.springframework.org/schema/beans
     
     
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     
     
    http://www.springframework.org/schema/tx
     
     
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
     
     
    http://www.springframework.org/schema/aop
     
     
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:

java 代码:<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="addressService" ref="addressService"/>
</bean>
<bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
    <property name="addressDao" ref="addressDao"/>
</bean>

2.3、事务相关配置:


java 代码:1
<tx:annotation-driven transaction-manager="txManager"/>
使用如上配置已支持声明式事务。3、修改测试方法并测试该配置方式是否好用:将 TransactionTest 类的 testServiceTransaction 测试方法拷贝一份命名为 testAnntationTransactionTest:classpath:chapter9/service/applicationContext-service-annotation.xml"

userService.save(user);
try {userService.save(user);
    Assert.fail();} catch (RuntimeException e) {
}
Assert.assertEquals(0, userService.countAll());
Assert.assertEquals(0, addressService.countAll());

4、执行测试,测试正常通过,说明该方式能正常工作,因为在 AnnotationAddressServiceImpl 类的 save 方法中抛出异常,因此事务需要回滚,所以两个 countAll 操作都返回 0。

9.4.7 @Transactional 配置详解
Spring 提供的 <tx:annotation-driven/> 用于开启对注解事务管理的支持,从而能识别 Bean 类上的 @Transactional 注解元数据,其具有以下属性:

transaction-manager:指定事务管理器名字,默认为 transactionManager,当使用其他名字时需要明确指定;
proxy-target-class:表示将使用的代码机制,默认 false 表示使用 JDK 代理,如果为 true 将使用 CGLIB 代理
order:定义事务通知顺序,默认 Ordered.LOWEST_PRECEDENCE,表示将顺序决定权交给 AOP 来处理。
Spring 使用 @Transactional 来指定事务属性,可以在接口、类或方法上指定,如果类和方法上都指定了 @Transactional,则方法上的事务属性被优先使用,具体属性如下:

value:指定事务管理器名字,默认使用 <tx:annotation-driven/> 指定的事务管理器,用于支持多事务管理器环境;
propagation:指定事务传播行为,默认为 Required,使用 Propagation.REQUIRED 指定;
isolation:指定事务隔离级别,默认为“DEFAULT”,使用 Isolation.DEFAULT 指定;
readOnly:指定事务是否只读,默认 false 表示事务非只读;
timeout:指定事务超时时间,以秒为单位,默认 - 1 表示事务超时将依赖于底层事务系统;
rollbackFor:指定一组异常类,遇到该类异常将回滚事务;
rollbackForClassname:指定一组异常类名字,其含义与 <tx:method> 中的 rollback-for 属性语义完全一样;
noRollbackFor:指定一组异常类,即使遇到该类异常也将提交事务,即不回滚事务;
noRollbackForClassname:指定一组异常类名字,其含义与 <tx:method> 中的 no-rollback-for 属性语义完全一样;
Spring 提供的 @Transactional 注解事务管理内部同样利用环绕通知 TransactionInterceptor 实现事务的开启及关闭。

使用 @Transactional 注解事务管理需要特别注意以下几点:

如果在接口、实现类或方法上都指定了 @Transactional 注解,则优先级顺序为方法 > 实现类 > 接口;
建议只在实现类或实现类的方法上使用 @Transactional,而不要在接口上使用,这是因为如果使用 JDK 代理机制是没问题,因为其使用基于接口的代理;而使用使用 CGLIB 代理机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的 @Transactional 注解是“不能继承的”;

              具体请参考基于 JDK 动态代理和 CGLIB 动态代理的实现 Spring 注解管理事务(@Trasactional)到底有什么区别。

在 Spring 代理机制下 (不管是 JDK 动态代理还是 CGLIB 代理),“自我调用”同样不会应用相应的事务属性,其语义和 <tx:tags> 中一样;
默认只对 RuntimeException 异常回滚;
在使用 Spring 代理时,默认只有在 public 可见度的方法的 @Transactional 注解才是有效的,其它可见度(protected、private、包可见)的方法上即使有 @Transactional 注解也不会应用这些事务属性的,Spring 也不会报错,如果你非要使用非公共方法注解事务管理的话,可考虑使用 AspectJ。

微信公众号【黄小斜】作者是蚂蚁金服 JAVA 工程师,专注于 JAVA
后端技术栈:SpringBoot、SSM 全家桶、MySQL、分布式、中间件、微服务,同时也懂点投资理财,坚持学习和写作,相信终身学习的力量!关注公众号后回复”架构师“即可领取
Java 基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的 Java 学习指南、Java 程序员面试指南等干货资源

正文完
 0