事务之三:编程式事务、声明式事务(XML配置事务、注解实现事务)
Spring2.0框架的事务处理有两大类:
JdbcTemplate操作采用的是JDBC默认的AutoCommit模式,也就是说我们还无法保证数据操作的原子性(要么全部生效,要么全部无效),如:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("UPDATE user SET age = 10 WHERE id = 'erica'");
jdbcTemplate.update("UPDATE user SET age = age+1 WHERE id = 'erica'");
由于采用了AutoCommit模式,第一个update操作完成之后被自动提交,数据库中”erica”对应的记录已经被更新,如果第二个操作失败,我们无法使得整个事务回滚到最初状态。对于这个例子也许无关紧要,但是对于一个金融帐务系统而言,这样的问题将导致致命错误。
为了实现数据操作的原子性,我们需要在程序中引入事务逻辑,在JdbcTemplate中引入事务机制,在Spring中有两种方式:
1 编码式事务
2 声明式事务
Spring支持编程式事务管理和声明式事务管理。许多Spring框架的用户选择声明式事务管理,因为这种方式和应用程序的关联较少,因此更加符合轻量级容器的概念。声明式事务管理要优于编程式事务管理,尽管在灵活性方面它弱于编程式事务管理,因为编程式事务允许你通过代码控制业务。
事务分为全局事务和局部事务。全局事务由应用服务器管理,需要底层服务器JTA支持(如WebLogic、WildFly等)。局部事务和底层采用的持久化方案有关,例如使用JDBC进行持久化时,需要使用Connetion对象来操作事务;而采用Hibernate进行持久化时,需要使用Session对象来操作事务。
Spring提供了如下所示的事务管理器。
这些事务的父接口都是PlatformTransactionManager。
Spring的事务管理机制是一种典型的策略模式,PlatformTransactionManager代表事务管理接口,该接口定义了三个方法,该接口并不知道底层如何管理事务,但是它的实现类必须提供getTransaction()方法(开启事务)、commit()方法(提交事务)、rollback()方法(回滚事务)的多态实现,这样就可以用不同的实现类代表不同的事务管理策略。使用JTA全局事务策略时,需要底层应用服务器支持,而不同的应用服务器所提供的JTA全局事务可能存在细节上的差异,因此实际配置全局事务管理器是可能需要使用JtaTransactionManager的子类,如:WebLogicJtaTransactionManager(Oracle的WebLogic服务器提供)、UowJtaTransactionManager(IBM的WebSphere服务器提供)等。
一、编码式事务
首先,进行以下配置,假设配置文件为(Application-Context.xml):
<beans> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>net.sourceforge.jtds.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value> </property> <property name="username"> <value>test</value> </property> <property name="password"> <value>changeit</value> </property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransac tionManager"> <property name="dataSource"> <ref local="dataSource" /> </property> </bean> <bean id="userDAO" class="net.xiaxin.dao.UserDAO"> <property name="dataSource"> <ref local="dataSource" /> </property> <property name="transactionManager"> <ref local="transactionManager" /> </property> </bean> </beans>
配置中包含了三个节点:
● dataSource
这里我们采用了apache dbcp组件提供的DataSource实现,并为其配置了JDBC驱动、数据库URL、用户名和密码等参数。
● transactionManager
针对JDBC DataSource类型的数据源,我们选用了DataSourceTransactionManager作为事务管理组件。
如果需要使用基于容器的数据源(JNDI),我们可以采用如下配置:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>jdbc/sample</value> </property> </bean> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTrans actionManager" />
● userDAO
userDAO 申明了一个UserDAO Bean,并为其指定了dataSource和transactionManger资源。
UserDAO对应的代码如下:
public class UserDAO { private DataSource dataSource; private PlatformTransactionManager transactionManager; public PlatformTransactionManager getTransactionManager() { return transactionManager; } public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public DataSource executeTestSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void insertUser() { TransactionTemplate tt = new TransactionTemplate(getTransactionManager()); tt.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { JdbcTemplate jt = new JdbcTemplate(executeTestSource()); jt.update( "insert into users (username) values ('xiaxin');"); jt.update( "insert into users (id,username) values(2,'erica');"); return null; } }); }
}
可以看到,在insertUser方法中,我们引入了一个新的模板类:org.springframework.transaction.support.TransactionTemplate。
TransactionTemplate封装了事务管理的功能,包括异常时的事务回滚,以及操作成功后的事务提交。和JdbcTemplate一样,它使得我们无需在琐碎的try/catch/finally代码中徘徊。在doInTransaction中进行的操作,如果抛出未捕获异常将被自动回滚,如果成功执行,则将被自动提交。
这里我们故意制造了一些异常来观察数据库操作是否回滚(通过在第二条语句中更新自增ID字段故意触发一个异常):
编写一个简单的TestCase来观察实际效果:
InputStream is = new FileInputStream("Application-Context.xml"); XmlBeanFactory factory = new XmlBeanFactory(is); UserDAO userDAO = (UserDAO) factory.getBean("userDAO"); userDAO.insertUser();
相信大家多少觉得上面的代码有点凌乱,Callback类的编写似乎也有悖于日常的编程习惯(虽然笔者觉得这一方法比较有趣,因为它巧妙的解决了笔者在早期自行开发数据访问模板中曾经遇到的问题)。
如何进一步避免上面这些问题?Spring 的容器事务管理机制在这里即体现出其强大的能量。
二、声明式事务
声明式事务又有三种实现方法:
1 (第一种) 最早的方法,用TransactionProxyFactoryBean,他是一个有AOP代理功能的FactoryBean.他返回的对象有事务.
还要在spring的配置文件XML中配置,比较麻烦,不详细说.
<!-- 事务测试DAO --> <bean id="go_TestPOAO" class="pic.dao.transaction_test.TestPOAOImpl" parent="go_POAOBase"></bean> <!-- 事务测试DAO 声明式事务管理 --> <bean id="go_TestPOAOProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="proxyInterfaces"> <list> <value>pic.dao.transaction_test.TestPOAO</value> </list> </property> <property name="target" ref="go_TestPOAO"/> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="insert*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
2 (第二种) 使用<tx:>来实现声明式事务 ,也要在spring的配置文件XML中配置,比较麻烦,不详细说.
<tx:advice id=""> ..... </tx:advice> <aop:config> ..... </aop:config>
采用声明式事务
1、声明式事务配置
* 配置SessionFactory
* 配置事务管理器
* 事务的传播特性
* 那些类那些方法使用事务
2、编写业务逻辑方法
* 继承HibernateDaoSupport类,使用HibernateTemplate来持久化,HibernateTemplate是Hibernate session的封装 * 默认的回滚是RuntimeException(包括继承RuntimeException的子类),普通异常不回滚
* 在编写业务逻辑方法时,最好将异常一直往上抛出,在呈现层处理(struts)
* spring的事务需要设置到业务方法上(事务边界定义到Facade类上),不要添加到Dao上
3 (第三种) 这个方法方便,使用注解来实现声明式事务, 下面详细说说这个方法:
第一步:引入<tx:>命名空间 ,在spring的配置文件中修改, beans根元素里多了三行,如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
第二步:在spring的配置文件中修改,将所有具有@Transactional 注解的bean自动配置为声明式事务支持
<!--JDBC事务管理器,根据你的情况使用不同的事务管理器,如果工程中有Hibernate,就用Hibernate的事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource"/> </property> </bean> <!-- 用注解来实现事务管理 --> <tx:annotation-driven transaction-manager="transactionManager"/>
第三步: 在接口或类的声明处 ,写一个@Transactional. 要是只的接口上写, 接口的实现类就会继承下来.
接口的实现类的具体方法,还可以覆盖类声明处的设置.
@Transactional public class TestPOAOImpl extends POAOBase implements TestPOAO { @Transactional(isolation = Isolation.READ_COMMITTED) public void test1() { String sql = "INSERT INTO sy_test (NAME,AGE) VALUES('注解赵云',30)"; execute(sql); sql = "INSERT INTO sy_test (NAME,AGE) VALUES('注解张飞',26)"; execute(sql); int a = 9 / 0; //异常 sql = "INSERT INTO sy_test (NAME,AGE) VALUES('注解关羽',33)"; execute(sql); System.out.println("走完了"); } //execute() 方法略... }
注意的几点:
1 @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.
2 默认情况下,一个有事务方法, 遇到RuntiomeException 时会回滚 . 遇到 受检查的异常 是不会回滚 的. 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .
@Transactional 的所有可选属性如下:
属性 | 类型 | 默认值 | 说明 |
propagation | Propagation枚举 | REQUIRED | 事务传播属性 (下有说明) |
isolation | isolation枚举 | DEFAULT | 事务隔离级别 (另有说明) |
readOnly | boolean | false | 是否只读 |
timeout | int | -1 | 超时(秒) |
rollbackFor | Class[] | {} | 需要回滚的异常类 |
rollbackForClassName | String[] | {} | 需要回滚的异常类名 |
noRollbackFor | Class[] | {} | 不需要回滚的异常类 |
noRollbackForClassName | String[] | {} | 不需要回滚的异常类名 |