【转】Spring事务管理
在 Spring 中,事务是通过 TransactionDefinition 接口来定义的。该接口包含与事务属性有关的方法。具体如清单 1 所示:
清单 1. TransactionDefinition 接口中定义的主要方法
public interface TransactionDefinition{
int getIsolationLevel();//isolation 隔离
int getPropagationBehavior();//Propagation 传播
int getTimeout();
boolean isReadOnly();
}
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。 TransactionDefinition 接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT :
这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是 TransactionDefinition.ISOLATION_READ_COMMITTED 。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED :
该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
TransactionDefinition.ISOLATION_READ_COMMITTED :
该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
TransactionDefinition.ISOLATION_REPEATABLE_READ :
该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE :
所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
补充 :
Sql 规范定义的四种事务隔离级别 , 不同的隔离级别采用不同的锁类开来实现 .
Read uncommited 未提交读
Read commited 提交读
Repeatable read 可重复读
Serializable 序列化
在四种隔离级别中 , Serializable 的级别最高 , Read Uncommited 级别最低 .
大多数数据库的默认隔离级别为 : Read Commited, 如 Sql Server , Oracle.
少数数据库默认的隔离级别为 Repeatable Read, 如 MySQL InnoDB 存储引擎
即使是最低的级别 , 也不会出现 第一类 丢失 更新问题 .
1. 脏读 ( 事务没提交,提前读取 ) :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
2. 不可重复读 ( 两次读的不一致 ) :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同 一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读 到的数据是不一样的,因此称为是不可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
3. 幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。 例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
4. 第一类更新丢失 ( 回滚丢失 ) :
当 2 个事务更新相同的数据源,如果第一个事务被提交,而另外一个事务却被撤销,那么会连同第一个事务所做的跟新也被撤销。也就是说第一个事务做的跟新丢失了。
5. 第二类更新丢失 ( 覆盖丢失 ) :
第二类更新丢失实在实际应用中经常遇到的并发问题,他和不可重复读本质上是同一类并发问题,通常他被看做不可重复读的特例:当 2 个或这个多个事务查询同样的记录然后各自基于最初的查询结果更新该行时,会造成第二类丢失更新。因为每个事务都不知道不知道其他事务的存在,最后一个事务对记录做的修改将覆盖其他事务对该记录做的已提交的跟新 ...
不可重复读的重点是修改 :
同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了
幻读的重点在于新增或者删除
同样的条件 , 第 1 次和第 2 次读出来的记录数不一样
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在 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 。
这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点( SavePoint )的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。
事务挂起
如:方法 A 支持事务,方法 B 不支持事务
方法 A 调用方法 B 。
在方法 A 开始运行时,系统为它建立 Transaction, 方法 A 中对于数据库的处理操作,会在该 Transaction 的控制之下。这时,方法 A 调用方法 B, 方法 A 打开的 Transaction 将挂起,方法 B 中任何数据库操作,都不在该 Transaction 的管理之下。当方法 B 返回,方法 A 继续运行,之前的 Transaction 恢复,后面的数据库操作继续在该 Transaction 的控制之下 提交或回滚。
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
事务的只读属性
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
事务的回滚规则
通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式, 也是 EJB 中的默认处理方式。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务。
Spring 事务管理 API 分析
最重要的有三个: TransactionDefinition 、 PlatformTransactionManager 、 TransactionStatus 。所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。
“给定的事务规则”就是用 TransactionDefinition 表示的,
“按照……来执行提交或者回滚操作”便是用 PlatformTransactionManager 来表示,
TransactionStatus 用于表示一个运行着的事务的状态 。打一个不恰当的比喻, TransactionDefinition 与 TransactionStatus 的关系就像程序和进程的关系。
TransactionDefinition
它用于定义一个事务。它包含了事务的静态属性,比如:事务传播行为、超时时间等等。 Spring 为我们提供了一个默认的实现类: DefaultTransactionDefinition ,该类适用于大多数情况。如果该类不能满足需求,可以通过实现 TransactionDefinition 接口来实现自己的事务定义。
public interface TransactionDefinition{
int getIsolationLevel();//isolation 隔离
int getPropagationBehavior();//Propagation 传播
int getTimeout();
boolean isReadOnly();
}
PlatformTransactionManager
用于执行具体的事务操作。接口定义如清单 2 所示:
清单 2. PlatformTransactionManager 接口中定义的主要方法
Public interface PlatformTransactionManager{
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status)throws TransactionException;
void rollback(TransactionStatus status)throws TransactionException;
}
根据底层所使用的不同的持久化 API 或框架, PlatformTransactionManager 的主要实现类大致如下:
* DataSourceTransactionManager :适用于使用 JDBC 和 iBatis 进行数据持久化操作的情况。
* HibernateTransactionManager :适用于使用 Hibernate 进行数据持久化操作的情况 。
* JpaTransactionManager :适用于使用 JPA 进行数据持久化操作的情况。
* 另外还有 JtaTransactionManager 、 JdoTransactionManager 、 JmsTransactionManager 等等。
如果我们使用 JTA 进行事务管理,我们可以通过 JNDI 和 Spring 的 JtaTransactionManager 来获取一个容器管理的 DataSource 。 JtaTransactionManager 不需要知道 DataSource 和其他特定的资源,因为它将使用容器提供的全局事务管理。而对于其他事务管理器,比如 DataSourceTransactionManager ,在定义时需要提供底层的数据源作为其属性,也就是 DataSource 。与 HibernateTransactionManager 对应的是 SessionFactory ,与 JpaTransactionManager 对应的是 EntityManagerFactory 等等。
TransactionStatus
PlatformTransactionManager.getTransaction( … ) 方法返回一个 TransactionStatus 对象。返回的 TransactionStatus 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。 TransactionStatus 接口提供了一个简单的控制事务执行和查询事务状态的方法。该接口定义如清单 3 所示:
清单 3. TransactionStatus 接口中定义的主要方法
public interface TransactionStatus{
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
}
编程式事务管理
Spring 的编程式事务管理概述
在 Spring 出现以前,编程式事务管理对基于 POJO 的应用来说是唯一选择。用过 Hibernate 的人都知道,我们需要在代码中显式调用 beginTransaction() 、 commit() 、 rollback() 等事务管理相关的方法,这就是编程式事务管理。通过 Spring 提供的事务管理 API ,我们可以在代码中灵活控制事务的执行。在底层, Spring 仍然将事务操作委托给底层的持久化框架来执行。
基于底层 API 的编程式事务管理
根据 PlatformTransactionManager 、 TransactionDefinition 和 TransactionStatus 三个核心接口,我们完全可以通过编程的方式来进行事务管理。示例代码如清单 4 所示:
public class BankServiceImpl implements BankService {
private BankDao bankDao;
private TransactionDefinition txDefinition;
private PlatformTransactionManager txManager;
......
public boolean transfer(Long fromId , Long toId , double amount) {
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
boolean result = false;
try {
result = bankDao.transfer(fromId , toId , amount);
txManager.commit(txStatus);
} catch (Exception e) {
result = false;
txManager.rollback(txStatus);
System.out.println("Transfer Error!");
}
return result;
}
}
相应的配置文件如清单 5 所示:
清单 5. 基于底层 API 的事务管理示例配置文件
<bean id="bankService" class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
<property name="txManager" ref="transactionManager "/>
<property name="txDefinition">
<bean class="org.springframework.transaction.support.DefaultTransactionDefinition ">
<property name="propagationBehaviorName " value="PROPAGATION_REQUIRED "/>
</bean>
</property>
</bean>
如上所示,我们在类中增加了两个属性:一个是 TransactionDefinition 类型的属性,它用于定义一个事务;另一个是 PlatformTransactionManager 类型的属性,用于执行事务管理操作。
如果方法需要实施事务管理,我们首先需要在方法开始执行前启动一个事务,调用 PlatformTransactionManager.getTransaction(...) 方法便可启动一个事务。创建并启动了事务之后,便可以开始编写业务逻辑代码,然后在适当的地方执行事务的提交或者回滚。
通过前面的示例可以发现,这种事务管理方式很容易理解,但令人头疼的是,事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交 / 回滚事务的样板代码。
声明式事务管理
Spring 的声明式事务管理概述
Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是 AOP 的用武之地。 Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。
和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
下面就来看看 Spring 为我们提供的声明式事务管理功能。
基于 TransactionProxyFactoryBean 的声明式事务管理
bankDao.transfer(fromId , toId , amount);
清单 9. 基于 TransactionProxyFactoryBean 的事务管理示例配置文件
<beans......>
......
<bean id="bankServiceTarget"
class="footmark.spring.core.tx.declare.classic.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<bean id="bankService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean ">
<property name="target" ref="bankServiceTarget "/>
<property name="transactionManager" ref="transactionManager "/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED </prop>
</props>
</property>
</bean>
......
</beans>
显式为每一个业务类配置一个 TransactionProxyFactoryBean 的做法将使得代码显得过于刻板
基于 <tx> 命名空间的声明式事务管理
前面两种声明式事务配置方式奠定了 Spring 声明式事务管理的基石。在此基础上, Spring 2.x 引入了 <tx> 命名空间,结合使用 <aop> 命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于 <aop> 命名空间的切点表达式支持,声明式事务也变得更加强大。
<beans......>
......
<bean id="bankService"
class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
ankDao" ref="bankDao"/>
</bean>
<tx:advice id="bankAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))" />
<aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut" />
</aop:config>
......
</beans>
由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。另外,如果配置的事务管理器 Bean 的名字取值为“ transactionManager ”,则我们可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为“ transactionManager ”。
基于 @Transactional 的声明式事务管理
除了基于命名空间的事务配置方式, Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及 @Transactional 标注。 @Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如清单 12 所示:
清单 12. 基于 @Transactional 的事务管理示例配置文件
@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId , Long toId , double amount) {
return bankDao.transfer(fromId , toId , amount);
}
Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,因此我们需要在配置文件中作如下声明来激活该后处理 Bean ,如清单 13 所示:
清单 13. 启用后处理 Bean 的配置
<tx:annotation-driven transaction-manager="transactionManager"/>
与前面相似, transaction-manager 属性的默认值是 transactionManager ,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected 、 private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
基于 <tx> 命名空间和基于 @Transactional 的事务声明方式各有优缺点。基于 <tx> 的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,而基于 @Transactional 的方式必须在每一个需要使用事务的方法或者类上用 @Transactional 标注,尽管可能大多数事务的规则是一致的,但是对 @Transactional 而言,也无法重用,必须逐个指定。另一方面,基于 @Transactional 的方式使用起来非常简单明了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。
如果不是对遗留代码进行维护,则不建议再使用基于 TransactionInterceptor 以及基于 TransactionProxyFactoryBean 的声明式事务管理方式,但是,学习这两种方式非常有利于对底层实现的理解。
利用 TransactionProxyFactoryBean 生成事务代理
<?xml version="1.0" encoding="gb2312"?>
<!-- Spring 配置文件的文件头,包含 DTD 等信息 -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- 定义数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 定义数据库驱动 -->
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<!-- 定义数据库 url-->
<property name="url"><value>jdbc:mysql://localhost:3306/spring</value></property>
<!-- 定义数据库用户名 -->
<property name="username"><value>root</value></property>
<!-- 定义数据库密码 -->
<property name="password"><value>32147</value></property>
</bean>
<!-- 定义一个 hibernate 的 SessionFactory-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- 定义 SessionFactory 必须注入 DataSource-->
<property name="dataSource"><ref local="dataSource"/></property>
<property name="mappingResources">
<list>
<!-- 以下用来列出所有的 PO 映射文件 -->
<value>Person.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<!-- 此处用来定义 hibernate 的 SessionFactory 的属性:
不同数据库连接,启动时选择 create,update,create-drop-->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 定义事务管理器,使用适用于 Hibernte 的事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<!-- HibernateTransactionManager bean 需要依赖注入一个 SessionFactory bean 的引用 -->
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<!-- 配置事务拦截器 -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<!-- 事务拦截器 bean 需要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义 BeanNameAutoProxyCreator, 该 bean 是个 bean 后处理器,无需被引用,因此没有 id 属性
这个 bean 后处理器,根据事务拦截器为目标 bean 自动创建事务代理
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
指定对满足哪些 bean name 的 bean 自动生成业务代理 -->
<property name="beanNames">
<!-- 下面是所有需要自动创建事务代理的 bean-->
<list>
<value>personDao</value>
</list>
<!-- 此处可增加其他需要自动创建事务代理的 bean-->
</property>
<!-- 下面定义 BeanNameAutoProxyCreator 所需的事务拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增加其他新的 Interceptor -->
</list>
</property>
</bean>
<!-- 定义 DAO Bean , 由于 BeanNameAutoProxyCreator 自动生成事务代理 -->
<bean id="personDao" class="lee.PersonDaoHibernate">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
</beans>
二 . 利用继承简化配置
大部分情况下,每个事务代理的事务属性大同小异,事务代理的实现类都是 TransactionProxyFactoryBean ,事务代理 bean 都必须注入事务管理器。
对于这种情况, Spring 提供了 bean 与 bean 之间的继承,可以简化配置。将大部分的通用配置,配置成事务模板,而实际的事务代理 bean ,则继承事务模板。这种配置方式可以减少部分配置代码,下面是采用继承的配置文件:
<!-- 配置事务模板,模板 bean 被设置成 abstract bean ,保证不会被初始化 -->
<bean id="txBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true" abstract="true">
<!-- 为事务模板注入事务管理器 -->
<property name="transactionManager"><ref bean="transactionManager"/></property>
<!-- 设置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 实际的事务代理 bean-->
<bean id="personDao" parent="txBase">
<!-- 采用嵌套 bean 配置目标 bean -->
<property name="target">
<bean class="lee.PersonDaoHibernate">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
</property>
</bean>
这种配置方式,相比前面直接采用 TransactionProxyFactoryBean 的事务代理配置方式,可以大大减少配置文件的代码量。每个事务代理的配置都继承事务模板,无需重复指定事务代理的实现类,无需重复指定事务传播属性——当然,如果新的事务代理有额外的事务属性,也可指定自己的事务属性,此时,子 bean 的属性覆盖父 bean 的属性。当然每个事务代理 bean 都必须配置自己的目标 bean ,这不可避免。
上面的配置可看出,事务代理的配置依然是增量式的,每个事务代理都需要单独配置——虽然增量已经减少,但每个事务代理都需要单独配置
用 BeanNameAutoProxyCreator 自动创建事务代理
下面介绍一种优秀的事务代理配置策略:采用这种配置策略,完全可以避免增量式配置,所有的事务代理由系统自动创建。容器中的目标 bean 自动消失,避免需要使用嵌套 bean 来保证目标 bean 不可被访问。这种配置方式依赖于 Spring 提供的 bean 后处理器,该后处理器用于为每个 bean 自动创建代理,此处的代理不仅可以是事务代理,也可以是任意的代理,只需要有合适的拦截器即可。
下面是采用 BeanNameAutoProxyCreator 配置事务代理的配置文件:
<?xml version="1.0" encoding="gb2312"?>
<!-- Spring 配置文件的文件头,包含 DTD 等信息 -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- 定义数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 定义数据库驱动 -->
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<!-- 定义数据库 url-->
<property name="url"><value>jdbc:mysql://localhost:3306/spring</value></property>
<!-- 定义数据库用户名 -->
<property name="username"><value>root</value></property>
<!-- 定义数据库密码 -->
<property name="password"><value>32147</value></property>
</bean>
<!-- 定义一个 hibernate 的 SessionFactory-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!-- 定义 SessionFactory 必须注入 DataSource-->
<property name="dataSource"><ref local="dataSource"/></property>
<property name="mappingResources">
<list>
<!-- 以下用来列出所有的 PO 映射文件 -->
<value>Person.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<!-- 此处用来定义 hibernate 的 SessionFactory 的属性:
不同数据库连接,启动时选择 create,update,create-drop-->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 定义事务管理器,使用适用于 Hibernte 的事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<!-- HibernateTransactionManager bean 需要依赖注入一个 SessionFactory bean 的引用 -->
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<!-- 配置事务拦截器 -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<!-- 事务拦截器 bean 需要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义 BeanNameAutoProxyCreator, 该 bean 是个 bean 后处理器,无需被引用,因此没有 id 属性
这个 bean 后处理器,根据事务拦截器为目标 bean 自动创建事务代理
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
指定对满足哪些 bean name 的 bean 自动生成业务代理 -->
<property name="beanNames">
<!-- 下面是所有需要自动创建事务代理的 bean-->
<list>
<value>personDao</value>
</list>
<!-- 此处可增加其他需要自动创建事务代理的 bean-->
</property>
<!-- 下面定义 BeanNameAutoProxyCreator 所需的事务拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增加其他新的 Interceptor -->
</list>
</property>
</bean>
<!-- 定义 DAO Bean , 由于 BeanNameAutoProxyCreator 自动生成事务代理 -->
<bean id="personDao" class="lee.PersonDaoHibernate">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
</beans>
TranscationInterceptor 是一个事务拦截器 bean ,需要传入一个 TransactionManager 的引用。配置中使用 Spring 依赖注入该属性,事务拦截器的事务属性通过 transactionAttributes 来指定,该属性有 props 子元素,配置文件中定义了三个事务传播规则:
所有以 insert 开始的方法,采用 PROPAGATION_REQUIRED 的事务传播规则。程序抛出 MyException 异常及其子异常时,自动回滚事务。所有以 find 开头的方法,采用 PROPAGATION_REQUIRED 事务传播规则,并且只读。其他方法,则采用 PROPAGATION_REQUIRED 的事务传播规则。
BeanNameAutoProxyCreator 是个根据 bean 名生成自动代理的代理创建器,该 bean 通常需要接受两个参数。第一个是 beanNames 属性,该属性用来设置哪些 bean 需要自动生成代理。另一个属性是 interceptorNames ,该属性则指定事务拦截器,自动创建事务代理时,系统会根据这些事务拦截器的属性来生成对应的事务代理。
为了让读者对这种配置方式有信息,对 PersonDaoHibernate 的 save 方法进行简单 修改,修改后的 save 方法如下:
/** *//**
* 保存人实例
* @param person 需要保存的 Person 实例
*/
public void save(Person person)
...{
getHibernateTemplate().save(person);
// 下面两行代码没有实际意义,仅仅为了引发数据库异常
DataSource ds = null;
DataSourceUtils.getConnection(ds);
}
在主程序中调用该 save 方法,主程序调用 save 方法的片段如下:
for (int i = 0 ; i < 10 ; i++ )
...{
// 保存 Person 实例
pdao.save(new Person(String.valueOf(i) , i + 10));
}
执行完主程序的该片段后,数据表不会插入任何记录。如果 BeanNameAutoProxyCreator 的配置修改成如下格式:
<!-- 定义 BeanNameAutoProxyCreator, 该 bean 是个 bean 后处理器,无需被引用,因此没有 id 属性
这个 bean 后处理器,根据事务拦截器为目标 bean 自动创建事务代理
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
指定对满足哪些 bean name 的 bean 自动生成业务代理 -->
<property name="beanNames">
<!-- 下面是所有需要自动创建事务代理的 bean-->
<list>
<!-- value>personDao</value-->
</list>
<!-- 此处可增加其他需要自动创建事务代理的 bean-->
</property>
<!-- 下面定义 BeanNameAutoProxyCreator 所需的事务拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value >
<!-- 此处可增加其他新的 Interceptor -->
</list>
</property>
</bean>
注意配置文中 beanNames 属性的变化,将所有 personDao 项注释,即不再为该 bean 生成事务代理。再次执行主程序,程序虽然抛出了数据库异常,但数据记录依然被插入数据库。
对比两次结果,这就是事务代理在其中的作用。
这种配置方式相当简洁,每次增加了新的 bean ,如果需要该 bean 的方法具有事务性,只需在 BeanNameAutoProxyCreator 的 beanNames 属性下增加一行即可,该行告诉 bean 后处理需要为哪个 bean 生成事务代理。