Spring事务管理
和EJB一样,Spring同时提供了编程式事务管理和声明式事务管理。Spring的编程式事务管理和EJB的极不相同。Spring使用了回调(callback)机制来抽象化具体实现。事实上,Spring并不需要JTA实现事务管理。如果应用程序只需要单个的持久化资源,那么Spring可以使用由持久化机制提供的事务管理,如JDBC,Hiberanate等。倘若应用程序需要访问多个资源,Spring通过使用第三方JTA的实现提供了分布式(XA)的事务管理。无论你使用编程式事务管理还是声明式事务管理,你都将用到Spring的事务管理器(transaction manager)。
·选择事务管理器
Spring并不是直接管理事务的。相反,它提供了一组事务管理器。这些事务管理器会将事务管理委托给一个与平台相关的事务实现,而这个实现可能是由JTA或持久化机制来完成。如果要使用事务管理器,我们需要在应用程序中声明它。下面让我们来看看最常用的事务管理器:DataSourceTransactionManager。
·JDBC事务
如果直接使用JDBC,DataSourceTransactionManager会帮你处理事务边界。使用方法如下:
<bean id=”transactionManager” class=”org.spring.framework.jdbc.
Datasource. DataSourceTransactionManager”>
<property name=”dataSource” ref=”dataSource” />
</bean>
在内部,DataSourceTransactionManager会调用DataSource获得的java.sql.Connection对象。
·Hibernate事务
写法和JDBC类似,只不过事务管理器换作HibernateTransactionManager
<bean id=”transactionManager” class=”org.springframework.
orm.hibernate. HibernateTransactionManager”>
<property name=”sessionPactory” ref=”sessionFactory” />
</bean>
上面是针对Hibernate2.x而言,如果使用Hibernate3.x,只需要修改class的值为”…orm.hibernate3.HibernateTransactionManager”即可。
后面的JPA,JDO,JTA都与之类似,都有属于它们自己的事务管理器,这里就不再赘述了。
·Spring的编程式事务
编程式事务是细粒度地控制事务的唯一方法。回顾RoadRantz的例子,我们现在增加一个方法addRant,具体实现如下:
public void addRant(Rant rant) {
rant.setPostedDate(new Date());
Vehicle rantVehicle = rant.getVehicle();
Vehicle existingVehicle =
rantDao.findVehicleByPlate(rantVehicle.getState(),
rantVechicle.getPlateNumber());
if (existingVehicle != null)
rant.setVehicle(existingVehicle);
else
rantDao.saveVihicle(existingVehicle);
rantDao.saveRant(rant);
}
从上面这段代码,我们可以发现有很多代码与保存Rant对象无关。代码会检查是否存在与rant相关联的Vihicle对象,如果没有的话保存Vihicle对象并与rant对象相关联,因此addRant方法必须是事务的。
添加事务的一种方法是利用Spring的TransactionTemplate以编程的方式直接添加事务边界。例如:
public void addRant(Rant rant) {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus ts) {
try {
rant.setPostedDate(new Date());
Vehicle rantVehicle = rant.getVehicle();
Vehicle existingVehicle =
rantDao.findVehicleByPlate(rantVehicle.getState(),
rantVechicle.getPlateNumber());
if (existingVehicle != null)
rant.setVehicle(existingVehicle);
else
rantDao.saveVihicle(existingVehicle);
rantDao.saveRant(rant);
}
catch(Exception e) {
ts.setRollbackOnly();
}
return null;
}
});
}
可以看到,在代码的开头我们实例化了一个TransactionTemplate对象,那么这个对象是从哪里来的呢?事实上,它是被注入到RantServiceImple bean中的:
<bean id=”rantService”
class=”com.roadrantz.service.RantServiceImpl” >
…
<property name=”transactionTemplate” >
<bean class=”org.springframework.transaction.support.
TransactionTemplate”>
<property name=”transactionManager” ref=”transactionManager”>
</property>
</bean>
</property>
</bean>
·Spring的声明式事务
Spring对于声明式事务管理的支持是由Spring的AOP框架提供的。Spring提供了三种方式来声明事务边界:使用AOP的代理bean、XML声明事务和注释驱动事务。
·1. 定义事务属性
事务属性有5个方面:只读、超时、回滚规则、隔离和传播。
传播属性定义了事务被调用方法和调用者方面的边界。Spring提供了很多常量来描述该属性,如PROPAGATION_MANDATORY, PROPAGATION_NESTED等,这些常量都被封装在org.springframework.transaction.TransactionDefinition接口中。
声明事务的第二个属性是隔离。隔离级别定义了事务活动是如何互相影响的。通常情况下,多个事务总是并发地运行的,它们共享相同的数据,这样就会产生下面的问题:读“脏”数据、多次读数据不一致以及读“假”数据。理想情况下,事务之间应该相互隔离,但是完美的事务隔离会影响应用程序性能,因为它需要锁行甚至是锁表。所以Spring提供了几个常量来表示不同隔离程度,例如:ISOLATION_DEFAULT,ISOLATION_READ_UNCOMMITTED等。这些变量都被封装在org.springframework.transaction.TransactionDefinition接口中。
事务的第三个属性是只读。如果只是对数据源进行读操作,那么数据源就可以进行某种程度的优化。因为只事务凯死后时数据源会进行只读优化,所以只有在那些会产生一个新的事务的方法中声明一个事务为只读的才有意义。
事务的第四个属性是超时属性。因为事务会将数据源锁定,所以长时间的事务运行将会导致资源浪费,所以你需要在一定时间之后自动地将事务回滚。同样地,只有在那些会产生一个新的事务的方法中声明一个事务的超时属性才有意义。
事务的最后一个属性是一组回滚规则。默认情况下,出现运行时异常即回滚事务,而出现检查异常时不会出现回滚。但是你可以指定出现某些检查异常时仍然进行回滚操作。当然你也可以指定当出现某些运行时异常也不进行回滚。 下面我们来看看如何使用这些属性。
·代理事务
Spring2.0之前的版本使用TransactionProxyFactoryBean来代理你的POJO从而实现声明式事务管理。下面给出一个例子:
<bean id=”rantService”
class=”org.springframework.transaction.interceptor.TransactionProxyFact
oryBean”>
<property name=”target” ref=”rantServiceTarget” />
<property name=”proxyInterfaces”
value=”com.roadrantz.service.RantService” />
<property name=”transactionManager” ref=”transactionManager” />
<property name=”transactionAttributes”>
<props>
<prop key=”add*”>PROPAGATION_REQUIRED</props>
<prop key=”*”>PROPAGATION_SUPPORT, readOnly</prop>
</props>
</property>
</bean>
transactionAttributes属性中,所有add开头的方法都要添加事务处理。所有其他方法都是只读。
·创建一个事务代理模板
只有一个服务bean时你当然可以使用TransactionProxyFactoryBean来做代理。但如果存在多个服务bean呢?难道它们都必须要进行事务处理?幸运地是,Spring可以创建抽象bean,这样你可以定义你的事务策略并重复使用它。首先,你需要创建一个TransactionProxyFactoryBean的抽象声明:
<bean id=”txProxyTemplate”
class=”org.springframework.transaction.interceptor.
TransactionProxyFactoryBean” abstract=”true”>
<property name=”transactionManager” ref=” transactionManager”/>
<property name=”transactionAttributes”>
<props>
<prop key=”add*”>PROPAGATION_REQUIRED</prop>
<prop key=”*”>PROPAGATION_SUPPORTS,
readOnly</prop>
</props>
</property>
</bean>
有了这个TransactionProxyFactoryBean的抽象声明,我们就可以利用它对任意数量的bean进行事务处理。方法如下:
<bean id=”rantService” parent=”txProxyTemplate”>
<property name=”target” ref=”rantServiceTarget” />
<property name=”proxyInterfaces”
value=”com.roadrantz.service.RantSerive” />
</bean>
·Spring2.0中的事务声明
使用TransactionProxyFactoryBean的问题在于它会导致Spring配置文件非常庞大(当然,使用代理模板除外),而且命名target bean的做法也不是很常见,容易令人混淆。因此,Spring2.0为事务声明提供了新的配置元素。这些元素在tx的命名空间,并且可以以xsd schema的方式添加到你的配置文件中,例如:
<beans xmlms=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
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-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd”>
可以看到,aop的命名空间也被加入其中,这是必须的。tx命名空间提供了一些新的XML配置元素,最常用的是<tx:advice>元素。下面的代码重写了刚才的例子:
<tx:advice id=”txAdvice”>
<tx:attributes>
<tx:method name=”add*” propagation=”REQUIRED” />
<tx:method name=”*” propagation=”SUPPORTS” read-only=”true” />
</tx:attributes>
</tx:advice>
即使你使用<tx:advice>你还是需要一个事务管理器。按照惯例,<tx:advice>假定事务管理器的id为transactionManager。如果你的事务管理器的id不是这个,你需要显式地在<tx:advice>指定。
<tx:advice id=”txAdvice” transaction-manager=”txManager” />
就<tx:advice>本身而言,它只定义了一个AOP advice,这个advice只是事务advice,而不是一个完整的事务切片。为了完整定义事务切片,我们必须定一个advisor,做法如下:
<aop:config>
<aop:advisor pointcut=”execution(* *..RantzService.*(..))”
advice-ref=”txAdvice” />
</aop:config>
pointcut属性使用了AspectJ(一种面向切片的框架,扩展了Java)的pointcut表达式来指定这个advisor会advise接口RantService中的所有方法。而txAdvice定义了哪些方法在实际运行以及对那些方法而言事务属性到底是什么。
Spring2.0中有比<tx:advice>更好用的,但它需要在Java5环境中才能工作,它就是注释驱动。
·定义注释驱动事务
<tx:advice>元素极大地简化了XML配置文件的编写,但注释驱动能更好地简化配置文件。tx命名空间提供了<tx:annotation-driven>元素,例如:
<tx:annotation-driven transaction-manager=”txManager” />
<tx:annotation-driven>配置元素会检查那些以@Transactional注释开头的bean,或者是类或者是方法,事务属性会定义在@Transactional注释的参数中,例如:
@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
public class RantServiceImpl implements RantService {
@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
public void addRant(Rant rant) {
…
}
…
}
@Transactional还可以用于接口前。如:
@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
public interface RantService {
…
@Transactional(propagation=Propagation.SUPPORTS, readOnly=false)
void addRant(Rant rant);
…
}
这就表明所有实现RantService接口的方法都应该进行事务处理。