Spring的事务管理不需要与任何特定的事务API耦合。对不同的持久层访问技术,编程式事务提供一致的事务编程风格,通过模板化的操作一致性的管理事务。声明式事务基于Spring AOP实现,却并不需要程序开发者成为AOP专家,亦可轻易使用Spring的声明式事务管理。
Java EE应用的传统事务有两种策略:全局事务和局部事务,全局事务由应用服务器管理,需要底层服务器的JTA支持。局部事务和底层所采用的持久化技术有关,当采用JDBC持久化技术时,需要使用Connection对象来操作事务;而采用Hibernate持久化技术时,需要使用Session对象来操作事务。
全局事务可以跨多个事务性的资源(典型的例子是关系数据库和消息队列);使用局部事务,应用服务器不需要参与事务管理,因此不能保证跨多个事务性资源的事务的正确性。当然,实际上大部分应用都使用单一事务性的资源。
Spring事务策略是通过PlatformTransactionManager接口体现的,该接口是Spring事务策略的核心。该接口的源代码如下:
public interface PlatformTransactionManager
{
// 平台无关的获得事务的方法
public abstract TransactionStatus getTransaction(TransactionDefinition transactiondefinition)
throws TransactionException;
// 平台无关的事务提交方法
public abstract void commit(TransactionStatus transactionstatus)
throws TransactionException;
// 平台无关的事务回滚方法
public abstract void rollback(TransactionStatus transactionstatus)
throws TransactionException;
}
PlatformTransactionManager是一个与任何事务策略分离的接口,随着底层不同事务策略的切换,应用必须采用不同的实现类。PlatformTransactionManager接口没有与任何事务资源捆绑在一起,它可以适用于任何的事务策略,结合Spring的IoC容器,可以向PlatformTransactionManager注入相关的平台特性。
PlatformTransactionManager接口有许多不同的实现类,应用程序面向与平台无关的接口编程,当底层采用不同的持久层技术时,系统只需使用不同的PlatformTransactionManager实现类即可——而这种切换通常由Spring容器负责管理,应用程序既无须与具体的事务API耦合,也无须与特定实现类耦合,从而将应用和持久化技术、事务API彻底分离开来。
Spring的事务管理机制是一种典型的策略模式,PlatformTransactionManager代表事务管理接口,但它并不知道底层如何管理事务,它只要求事务管理需要提供开始事务、提交事务和回滚事务三个方法,但具体如何实现则交给其实现类来完成。
在PlatformTransactionManager接口内,包含一个getTransaction(TransactionDefinition transactiondefinition)方法,该方法根据一个TransactionDefinition参数,返回一个TransactionStatus对象。TransactionStatus 对象表示一个事务,TransactionStatus 被关联在当前执行的线程上。
getTransaction(TransactionDefinition transactiondefinition)返回的TransactionStatus对象,可能是一个新的事务,也可能是一个已经存在的事务对象。如果当前执行的线程已经处于事务管理下,则返回当前线程的事务对象;否则,系统将新建一个事务对象后返回。
TransactionDefinition 接口定义了一个事务规则,该接口必须指定如下几个属性值:
☞ 事务隔离:当前事务和其他事务的隔离程度。例如,这个事务能否看到其他事务未提交的数据等。
☞ 事务传播:通常,在事务中执行的代码都会在当前事务中运行。但是,如果一个事务上下文已经存在,有几个选项可指定该事务性方法的执行行为。例如,大多数情况下,简单地在现有的事务上下文中运行;或者挂起现有事务,创建一个新的事务。
☞ 事务超时:事务在超时前能运行多久,也就是事务的最长持续时间。如果事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚事务。
☞ 只读状态:只读事务不修改任何数据。在某些情况下(例如使用Hibernate时),只读事务是非常有用的优化。
TransactionStatus代表事务本身,它提供了简单的控制事务执行和查询事务状态的方法,这些方法在所有的事务API中都是相同的。TransactionStatus接口的源代码如下:
public interface TransactionStatus
{
// 判断事务是否为新建的事务
boolean isNewTransaction();
// 设置事务回滚
public abstract void setRollbackOnly();
// 查询事务是否已有回滚标志
public abstract boolean isRollbackOnly();
}
Spring具体的事务管理由PlatformTransactionManager的不同实现类完成。在Spring容器中配置PlatformTransactionManager Bean时,必须针对不同环境提供不同的实现类。
下面提供了不同的持久层访问环境,及其对应的PlatformTransactionManager实现类的配置。
JDBC数据源的局部事务策略的配置文件如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1/javaee" />
<property name="user" value="root" />
<property name="password" value="123456" />
<property name="maxPoolSize" value="40" />
<property name="minPoolSize" value="1" />
<property name="initialPoolSize" value="1" />
<property name="maxIdleTime" value="20" />
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
beans>
容器管理的JTA全局事务的配置文件如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/jpetstore" />
bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
beans>
从上面的配置文件来看,当配置JtaTransactionManager全局事务管理策略时,只需指定事务管理器实现类即可,无须传入额外的事务资源。这是因为全局事务的JTA资源由Java EE服务器提供,而Spring容器能自行从Java EE服务器中获取该事务资源,所以无须使用依赖注入来配置。
当采用Hibernate持久层访问策略时,局部事务策略的配置文件如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1/javaee" />
<property name="user" value="root" />
<property name="password" value="123456" />
<property name="maxPoolSize" value="40" />
<property name="minPoolSize" value="1" />
<property name="initialPoolSize" value="1" />
<property name="maxIdleTime" value="20" />
bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list>
<value>lee/MyTest.hbm.xmlvalue>
list>
property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialectprop>
<prop key="hibernate.hbm2ddl.auto">updateprop>
props>
property>
bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
bean>
beans>
如果底层采用Hibernate持久层技术,但事务采用JTA全局事务,则Spring配置文件如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/jpetstore" />
bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list>
<value>lee/MyTest.hbm.xmlvalue>
list>
property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialectprop>
<prop key="hibernate.hbm2ddl.auto">updateprop>
props>
property>
bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
beans>
从上面的配置文件可以看出,不论采用哪种持久层访问技术,只要使用JTA全局事务,Spring事务管理的配置完全一样,因为他们采用的都是全局事务管理策略。
从上面的配置文件可以看出,当采用Spring事务管理策略时,应用程序无须与具体的事务策略耦合。Spring提供了如下两种事务管理方式:
☞ 编程式事务管理:即使利用Spring编程式事务时,程序也可直接获取容器中的transactionManager Bean,该Bean总是PlatformTransactionManager的实例,所以可以通过该接口所提供的3个方法来开始事务、提交事务和回滚事务。
☞声明式事务管理:无须在Java程序中书写任何的事务操作代码,而是通过在XML文件中为业务组件配置管理事务代理(AOP代理的一种),AOP为事务代理所织入的增强处理也有Spring提供:在目标方法执行之前,织入开始事务;在目标方法执行之后,织入结束事务。
Spring同时支持编程式事务策略和声明式事务策略,大部分时候,我们都推荐采用声明式事务策略。使用声明式事务策略的优势十分明显:
☞ 声明式事务能大大降低开发者的代码书写量,而且声明式事务几乎不影响应用的代码。因此,无论底层的事务策略如何变化,应用程序都无须任何改变。
☞ 应用程序代码无须任何事务处理代码,可以更专注于业务逻辑的实现。
☞ Spring则可对任何POJO的方法提供事务管理,而且Spring的声明式事务管理无须容器的支持,可在任何环境下使用。
☞ EJB的CMT无法提供声明式回滚规则:而通过配置文件,Spring可指定事务在遇到特定异常时自动回滚。Spring不仅可在代码中使用setRollbackOnly回滚事务,也可在配置文件中配置回滚规则。
☞ 由于Spring采用AOP的方式管理事务,因此,可以在事务回滚动作中插入用户自己的动作,而不仅仅是执行系统默认的回滚。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1/javaee" />
<property name="user" value="root" />
<property name="password" value="123456" />
<property name="maxPoolSize" value="40" />
<property name="minPoolSize" value="1" />
<property name="initialPoolSize" value="1" />
<property name="maxIdleTime" value="20" />
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">**
<property name="dataSource" ref="dataSource" />
bean>
<bean id="newsDao" class="org.crazyit.app.dao.impl.NewsDaoImpl">
<property name="ds" ref="dataSource"/>
bean>
<bean id="newsDaoTrans" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="target" ref="newsDao"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIREDprop>
props>
property>
bean>
beans>
上面的配置文件中第一段粗体字代码配置了一个事务管理器,该事务管理器是针对JDBC局部事务的特定实现类。程序第二段粗体字代码为test Bean配置了事务代理。
配置事务代理时需要传入一个事务管理器、一个目标Bean,并指定该事物代理的事务属性,事务属性由transactionManager属性指定。上面事务属性只有一条事务传播规则,该规则指定对于所有方法都使用PROPAGATION_REQUIRED的传播规则。Spring支持的事务传播规则如下:
☞ PROPAGATION_MANDATORY:要求调用该方法的线程必须处于事务环境中,否则抛出异常
☞ PROPAGATION_NESTED:如果执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。如果执行该方法的线程并未处于事务中,也启动新的事务,然后执行该方法,此时与PROPAGATION_REQUIRED相同
☞ PROPAGATION_NEVER:不允许调用该方法的线程处于事务环境系下,如果调用该方法的线程处于事务环境下,则抛出异常
☞ PROPAGATION_NOT_SUPPORTED:如果调用该方法的线程处于事务中,则先暂停当前事务,然后执行该方法
☞ PROPAGATION_REQUIRED:要求在事务环境中执行该方法,如果当前执行线程已处于事务中,则直接调用;如果当前执行线程不处于事务中,则启动新的事务后执行该方法
☞ PROPAGATION_REQUIRED_NEW:该方法要求在新的事务环境中执行,如果当前执行线程已处于事务中,则先暂停当前事务,启动新事务后执行该方法;如果当前调用线程不处于事务中,则启动新的事务后执行方法
☞ PROPAGATION_SUPPORTS:如果当前执行线程处于事务中,则使用当前事务,否则不使用事务
事实上,Spring不仅支持对接口的代理,整合CGLIB后,Spring甚至可以对具体类生成代理,只要设置proxy-target-class属性为true就可以。如果目标Bean没有实现任何接口,proxy-target-class属性默认被设为true,此时Spring会对具体类生成代理。
<aop:aspectj-autoproxy proxy-target-class="true" />
虽然前面介绍的TransactionProxyFactoryBean配置策略简单易懂,但配置起来极为繁琐:每个目标Bean都需要额外配置一个TransactionProxyFactoryBean代理,这种方式将导致配置文件急剧增加。
Spring2.X的XML Schema方式提供了更简洁的事务配置策略,Spring2.X提供了tx命名空间来配置事务管理,tx命名空间下提供了
元素来配置事务增强处理,一旦使用该元素配置了事务增强处理,就可以直接使用
为容器中一批Bean配置自动事务代理。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-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">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1/javaee" />
<property name="user" value="root" />
<property name="password" value="123456" />
<property name="maxPoolSize" value="40" />
<property name="minPoolSize" value="1" />
<property name="initialPoolSize" value="1" />
<property name="maxIdleTime" value="20" />
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<bean id="newsDao" class="org.crazyit.app.dao.impl.NewsDaoImpl">
<property name="ds" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* org.crazyit.app.dao.iml.*Impl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
aop:config>
beans>
上面的配置文件的第一段粗体字代码使用XML Schema启用了Spring配置文件的tx、aop两个命名空间,配置文件的第二段粗体字代码配置了一个事务增强处理,配置
元素时只需指定一个transaction-manager属性,该属性的默认值是‘transactionManager’
配置文件中最后一段粗体字是
定义,它确保由txAdvice切面定义事务增强处理能在合适的点被织入。首先我们定义了一个切入点,它匹配org.crazyit.app.dao.iml包下所有以Impl结尾的类所包含的所有方法,我们把该切入点叫做myPointcut。然后用一个Advisor把这个切入点与txAdvice绑定在一起,表示当myPointcut执行时,txAdvice定义的增强处理将被织入。
当我们使用这种配置策略时,无须为每个业务Bean专门配置事务代理,Spring AOP会为业务组件自动生成代理,程序可以直接请求容器中test Bean,该Bean的方法已经具有了事务性——因为该Bean的实现类位于org.crazyit.app.dao.iml包下,且以Impl结尾,和myPointcut切入点匹配。
配置
子元素可以指定如下几个属性:
☞ name:必选属性,与该事务语义关联的方法名。该属性支持视同通配符
☞ propagation:指定事务传播行为,该属性值可为Propagation枚举类的任一枚举值。该属性的默认值为Propagation.REQUIRED
☞ isolation:指定事务隔离级别,该属性值可为Isolation枚举类的任一枚举值,该属性的默认值为Isolation.DEFAULT
☞ timeout:指定事务超时的时间(以秒为单位),指定-1意味着不超时,该属性的默认值是-1
☞ read-only:指定事务是否只读。该属性的默认值是false
☞ rollback-for:指定出发事务回滚的异常类(应使用全限定类名),该属性可指定多个异常类,多个异常类之间以英文逗号隔开
☞ no-rollback-for:指定不触发事务回滚的异常类(应使用全限定类名),该属性可指定多个异常类,多个异常类之间以英文逗号隔开
提示:在默认情况下,只有当抛出运行时异常和unChecked异常时,Spring事务框架才会自动回滚事务。也就是说,只有当抛出一个RuntimeException或其子类实例,以及Error对象,Spring才会自动回滚事务。如果事务方法中抛出Checked异常,则事务不会自动回滚。
通过使用rollback-for属性可强制Spring遇到特定Checked异常时自动回滚事务,下面的XML配置片段示范了这种用法。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="exception.NoItemException"/>
<tx:method name="*"/>
tx:attributes>
tx:advice>
如果想让事务方法抛出指定异常时强制不回滚事务,则可通过no-rollback-for属性来指定,如下面的配置片段所示。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" no-rollback-for="exception.NoItemException"/>
<tx:method name="*"/>
tx:attributes>
tx:advice>
Spring还允许将事务配置在设置在Java类定义中,这需要借助于@Transactional,该注解既可用于修饰Spring Bean类,也可用于修饰Bean类中的某个方法。
如果使用@Transactional修饰Bean类,表明这些事务设置对整个Bean类起作用;如果使用@Transactional修饰Bean类的某个方法,表明这些事务设置只对该方法有效。
使用@Transactional时可指定如下方法。
☞ isolation:用于指定事务隔离的级别。默认为底层事务的隔离级别
☞ noRollbackFor:指定遇到指定异常时强制不回滚事务
☞ noRollbackForClassName:指定遇到指定多个异常时强制不回滚事务。该属性值可以指定多个异常类名
☞ propagation:指定事务传播属性
☞ readOnly:指定事务是否只读
☞ rollbackFor:指定遇到指定异常时强制回滚事务
☞ rollbackForClassName:指定遇到指定多个异常时强制回滚事务。该属性值可以指定多个异常类名
☞ timeout:指定事务的超时时长
根据上面的解释不难看出,其实该Annotation所指定的属性与
元素中能指定的事务属性基本上是对应的,他们的意义也基本相似。
下面使用@Transactional修饰需要添加事务的方法
public class NewsDaoImpl implements NewsDao {
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert(String title, String content) {
.........
}
}
上面的Bean类中insert()方法使用了@Transactional修饰,表明该方法就会具有事务性。仅使用这个Annotation修饰还不够,还需要让Spring根据Annotation来配置事务代理。所以还需要在Spring配置文件中增加如下配置片段:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>