一、简介
在Spring和EJB中事务管理都是业务逻辑开发的重点,在spring2和EJB3之后都对事务管理进行了简化,更多得采用声明式(可通过注解)接口来开发事务。在典型的事务管理中,主要的关注点有三个:事务传播、事务隔离和事务回滚。
事务传播是指事务上下文Transactional Context如何在调用链中传播;事务隔离是指并发的事务相互之间不产生干扰;事务回滚涉及当产生异常时,系统如何响应,在什么样的情况下将事务进行回滚。
笔记中主要记录上述三个概念以及比较在spring2.5和EJB3中不同的处理方法。
二、事务传播
当一个Bean的方法在事务上下文(Transactional Context)中执行时,它可以将自己所在的Transactional Context传播到它所调用的方法中。事务传播的规则由事务属性定义,通常的事务属性分为如下六个值:
NotSupported/Supports
Required/RequiresNew
Mandatory/Never
他们的意义如下:
NotSupported/Supports 事务会在调用具有NotSupported属性的方法时被挂起,直到方法执行完毕,即事务不会被传入具有NotSupported属性及其调用的方法中,当该方法执行完毕后原事务将恢复;Supports事务属性表明,当一个Bean的方法在某个事务范围内被调用时,该方法将加入这个事务范围,同时它也可以和未包含在事务范围内的Bean交互。
Required/RequiresNew Required属性表明该方法必须在一个事务范围内执行,若发起调用的客户端属于一个事务范围内,则该Bean也会将自己加入该事务范围。若调用者不属于任何事务范围,则Required Bean将发起一个新的事务,当方法执行完之后事务随即结束。
RequiresNew表明,当对该Bean的方法调用时,总会产生一个新的事务范围,当方法结束时事务也会终止。
Mandatory/Never Mandatory表明方法必须在发起调用的客户端的事务范围内执行,若调用端不属于任何事务范围,则调用将失败;Never则相反,它表明方法决不能在事务范围内被调用,若调用端属于某个事务范围,则调用将失败。
事务的传播过程将由环境来监控,如果传播过程中任意一项操作失败,则整个事务就会失败。
三、事务隔离
事务的隔离性条件是指当多个事务对相同数据进行操作可能的结果,隔离条件包括脏读、不可重复读、幻读。
脏读 当某一事务读取了另一事务修改之后尚未提交的数据时,就会产生脏读问题。若另一事务被回滚,则它所做的修改将被撤销,此前读取的数据将会是一个无效值。
不可重复读 在某一事务两次读取同一数据之间,另一事务修改了该数据,导致两次读取的数据不同,这就是不可重复读问题。
幻读 如果添加到数据库中的新数据在执行实际的插入操作之前可以被其他的事务检测到,就会出现幻读问题。
可以通过使用数据库的不同锁定技术来解决如上的三个问题,常见的三种类型的锁如下:读锁、写锁、排他写锁。
对应的,数据库中有如下几个隔离级别:
Read Uncommitted 事务可以读取未被提交的数据,可能产生上述三种问题。
Read Committed 事务不能读取未被递交的数据,可以避免脏读。
Repeatable Read 事务不能修改其他事务正在读取的数据,可避免脏读和不可重复读问题
Serializable 将事务进行串行化处理,可以避免所有的问题
以上几个隔离级别隔离性越强,对性能的影响就越大,需要根据实际业务做出平衡。
四、异常和事务
事务管理中常常将异常分为两类:系统异常和应用异常。
系统异常 代表未知的内部错误,当应用服务器发生内部错误时往往就会抛出系统异常,当希望终止业务流程的执行时,业务逻辑也可以抛出系统异常。系统异常包括java.lang.RuntimeException及其子类,当业务逻辑中抛出系统异常时一定会引起事务的回滚。
应用异常 应用异常往往代表一种业务逻辑的错误,它会被抛出到调用端以做出适当的响应,默认情况下应用异常并不会引起事务的回滚。
五、Spring中的事务管理
Spring框架为事务处理提供了一致的抽象,它为复杂的事务API(如JTA、Hibernate、JDBC)提供了一致的编程模型,并支持声明式事务。
Spring提供了一个抽象的事务策略接口:org.springframe.transaction.PlatformTransactionManager
public interface PlatformTransactionManager{
TransactionStatus getTransaction(TransactionDefinition d)
throws TransactionException;
void commit(TransactionStatus s) throws TransactionException;
void rollback(TransactionStatus s) throws TransactionException;
}
这是一个SPI接口,可以使用JTA、Hibernate、JDBC等方法实现,最终在框架中提供了统一的事务API。
在Spring中可以用两种配置方式使用事务:基于XML的配置和Annotations的配置
5.1、基于XML的事务配置
首先要定义一个正确的PlatformTransactionManager实现,如采用Hibernate的实现定义如下:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mappingResources"> <list> <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=${hibernate.dialect} </value> </property> </bean> <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
Spring的声明式事务支持是通过Spring AOP代理来实现的,事务方面的代码与Spring绑定并以一种模板风格的方式使用(如HibernateTemplate),AOP代理使用一个PlatformTransactionManager的具体实现配合TransactionInterceptor在方法调用前后实施事务管理。
在XML的声明式事务配置中也使用了AOP类似的配置方法
<bean id="fooService" class="x.y.service.DefaultFooService"/> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/> </aop:config>
上述配置中,<tx:advice />定义了一个事务通知,在<aop:config />中将该通知应用到合适的点上。
5.2、使用@Transactional配置
除了使用XML配置外,Spring还支持基于注解的事务配置。可以使用@Transactional注解定义Bean的事务属性
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
同时需要在xml配置文件中定义注解驱动:
<bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="txManager"/> <!-- a PlatformTransactionManager is still required --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) --> <property name="dataSource" ref="dataSource"/> </bean>