摘 要: Spring提供了多样化的事务编程支持,包括编程式事务和声明式事务。针对JDBC的事务处理对各类实现方式进行了介绍,并对实现效率进行了比较。当事务处理量少时,可以考虑用基于JDBC模板的编程式事务,当事务涉及量大时建议用基于@Transactional注解的声明式事务。
关键词: Spring 3.x;事务处理;@Transactional注解;JDBC模板
事务是访问数据库的一个基本单位,通常用一个操作序列表示。事务ACID特性,即原子性、一致性、独立性和持久性。一个事务处理包括一系列操作,并把它们当做整体失败或成功的单个操作对待。传统JDBC的Connection类中含有几个方法实现对事务的处理支持,setAutoCommit方法设置事务是否自动提交,commit方法进行事务提交,rollback方法实现事务回滚。传统的JDBC事务编程将程序代码与事务操作代码耦合在一起。而Spring面向的是各种数据访问方式,包括JDBC、Hibernate、JTA、JTS等。因此,需要一个统一的策略。
Spring 是一个以控制倒转(IoC)原则和面向方面编程思想(AOP)为基础的轻量级框架。Spring因其优良的性能、强大的功能吸引了众多应用开发者的兴趣。Spring提供的事务管理可以分为编程式和声明式两类[1]。根据事务涉及范围,可将事务分为基于JTA的全局事务和针对具体数据库操作的局部事务。而在具体数据库操作方式上,Spring既实现了基于JDBC的事务处理,又实现了基于Hibernate的事务处理。本文以JDBC事务为样例进行介绍。
1 Spring的编程式事务处理
Spring提供了几个关于事务处理的接口,在编程时常用。它们是:
(1)TransactionDefinition:定义事务属性的接口;
(2)TransactionStatus:定义事务管理状态的接口;
(3)PlatformTransactionManager:用于管理事务的基础接口。
Spring的事务管理是一种典型的策略模式,PlatformTransactionManager代表事务管理接口,但它不知道底层如何管理事务,只要求管理者提供开始事务(getTransaction)、提交事务(commit)和回滚事务(rollback)三个方法,具体实现交给实现类完成。JDBC事务的具体实现类是DataSourceTransactionManager。
编程式事务可分为两种实现方式,一种是由TransactionTemplate进行事务管理,另一种是由JdbcTemplate进行事务管理。程序中用到的事务管理器可通过配置注入。例如:
<!—指定事务管理器 –->
<bean id="myTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource"/> <!—dataSource的注入配
置略 -->
</property>
</bean>
<bean id=”userDAO” class=” ecjtu.userDAO”> <!—业务逻辑Bean -->
<property name=”dataSource”>
<ref bean=”dataSource”/>
</property>
<property name=”transactionManager”> <!—将事务管理
器注入业务逻辑—>
<ref bean=”myTransactionManager”>
</property>
</bean>
1.1 由TransactionTemplate进行事务处理
Spring的TransactionTemplate在编程中可省去事务提交、回滚代码。只需将访问数据库的操作代码安排在TransactionTemplate的处理框架内,Spring 将自动进行 commit 和 rollback处理。TransactionTemplate是采用回调机制实现方法调用。
具体实现的核心代码如下,代码中的事务管理器通过配置注入。
TransactionTemplate transactionTemplate=
new TransactionTemplate(mytransactionManager);
transactionTemplate.execute(
new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(Transaction
Status status) {
…… // 这里安排业务逻辑处理
}
}
);
1.2 由JdbcTemplate的执行结果决定事务提交和回滚
为简化对JDBC数据源的访问处理,Spring提供了JdbcTemplate类。基于JdbcTemplate的事务处理与传统的JDBC事务非常类似,它根据程序执行SQL是否出现异常决定事务是提交还是回滚。具体实现框架如下:
DefaultTransactionDefinition def=new DefaultTransactionDefinition();
TransactionStatus status= transactionManager.getTransaction(def); try {
JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource); for ( int k=0;k<usernames.length;k++) { //批量插入若干帐户
String sql="insert into user values(′"+prefix+k+"′,′"+
"111111′,’’,′"+usernames[k]+ "′,10) ";
jdbcTemplate.execute(sql);
//通过JdbcTemplate执行SQL语句
}
transactionManager.commit(status); //事务提交
}
catch( DataAccessException ex) { //出现数据访问异常
transactionManager.rollback(status); //事务回滚
}
2 Spring声明式事务处理
Spring声明式事务处理使用了IoC和AOP思想,因此,需要将相关的JAR包加入到类路径中。声明式事务处理不需要在具体业务逻辑中加入任何事务处理代码。它是通过在配置文件中用属性设置来声明业务逻辑中的哪些操作需要进行事务处理[2]。
后续配置中数据源、事务管理器、userDAO的定义和前面的相同,为节省篇幅予以省略。以下分别讨论声明式事务的几种实现方式。
2.1 用事务拦截器进行事务管理
Spring的事务拦截器(TransactionInterceptor)通过对方法执行的拦截加入事务管理。TransactionInterceptor类有两个主要的属性: transactionManager属性实现与事务管理器的关联;transactionAttributes 属性用来定义事务规则。
<bean id = "transactionInterceptor" class=
"org.springframework.transaction.interceptor.Transaction-
Interceptor">
<property name="transactionManager">
<ref bean="myTransactionManager"/>
</property>
<property name="transactionAttributeSource">
<value>ecjtu.UserDao. insertUsers *=PROPAGATION_RE-
QUIRED</value>
</property>
</bean>
说明:上面配置指明对于ecjtu.UserDao类的insertUsers方法使用PROPAGATION_REQUIRED的事务传播规则进行事务处理。该传播特性的意思是,如果执行方法前已在事务中,则利用该事务,否则创建一个新事务来执行方法。
2.2 用TransactionProxyFactoryBean进行事务管理
TransactionProxyFactoryBean创建了一个JDK代理,该代理会拦截目标对象的方法调用。对于名字出现在transactionAttributes属性的key中的任何方法,代理会使用指定的传播方式来开启一个事务。以下为配置举例。
<bean id="userDAOProxy"
class="org.springframework.transaction.interceptor.Transaction-
ProxyFactoryBean">
<property name="transactionManager">
<ref bean="myTransactionManager "/>
</property>
<property name="target">
<ref bean="userDAO"/>
</property>
<property name="transactionAttributes">
<props>
<!-- 表示对所有以insert和save开头的方法
进行事务处理 -->
<prop key="insert*">PROPAGATION_REQUIRED</
prop>
<prop key="save*">PROPAGATION_REQUIRED</
prop>
</props>
</property>
</bean>
该配置方式的明显缺点是要为每个有事务处理要求的bean配置一个代理bean ,这样配置文件的内容增加非常快。
2.3 使用@Transactional注解
Spring还提供了@Transactional全注解的事务配置,它具有功能强大、简单明了的特点。@Transactional注解可修饰类,也可修饰方法。修饰类表示对整个类起作用;修饰方法则仅对方法起作用[3]。
例如,以下代码在insertUsers方法上加上@Transactional注解,则该方法在执行过程中将自动进行事务处理。
public class UserDaoImpl implements UserDao {
@Transactional (propagation = Propagation.REQUIRED)
public void insertUsers(String prefix, String[ ]usernames) {
for (int k=0;k<usernames.length;k++)
register(prefix+k,"111111","",usenames[k],10);
}
}
2.3.1 相关配置
要让Spring启用对annotation的支持,在beans.xml配置文件中要有如下行:
<context:annotation-config />
为了让Spring根据Annotation来配置事务代理,还需要指定事务管理器。
<tx:annotation-driven transaction-manager=" myTransactionManager "/>
2.3.2 使用@Transactional的几点注意
(1)声明式事务配置后,相关Bean将建立事务代理。所以从系统环境获取Bean和访问Bean均要通过接口,不要通过具体实现类来获取Bean。
(2)@Transactional只能被应用到public方法上,实际接口中的方法均为public,除非不属于接口的方法。
(3)默认情况下,一个有事务的方法, 遇到RuntimeException 时会回滚,遇到受检查的异常是不会回滚的。要想所有异常都回滚,要加上rollbackFor属性来指定。例如:
@Transactional(rollbackFor=Exception.class)
3 几种事务管理的测试对比
以下对几种事务处理方法进行测试对比,参见表1。实验选择批量插入30个学生,分别对正常插入操作完成和出现异常导致回滚所花费时间代价进行测试。测试结果表明,在声明式事务处理中,使用Spring的事务注解方式执行效率高,而在编程式事务处理中,基于JDBC模板执行结果进行事务处理执行效率也较好,但编程稍复杂。
Spring提供了多样化的事务编程支持,包括编程式事务和声明式事务。任何事务处理方式都需要有事务管理器的支持。Spring的编程式事务适合对局部操作进行事务处理。如果应用系统中有大量的事务需要处理,则应该使用声明式事务处理。声明式事务处理实现了事务处理与业务逻辑的分离,声明式事务处理依赖Spring AOP的功能。在3种声明式处理中,用TransactionProxyFactoryBean实现的事务处理,需要为涉及事务处理的每个Bean设置一个代理,配置量大。用事务拦截器方式执行效率不够理想。而基于@Transactional注解的事务定义方法具有配置简单、编程和执行效率高的特点,是值得推荐的方式。
参考文献
[1] 李涛,张波,张晓鹏,等. Spring 框架的事务管理应用分析[J].计算机与信息技术,2006(7):8-9.
[2] 李刚.轻量级Java EE企业应用实践(第3版)-Struts2+Spring3+Hibernate整合开发[M].北京:北京电子工业出版社,2011.
[3] 刘荣辉.基于Annotation的Spring事务应用设计[J].微型电脑应用,2009,25(7):57-59.