数据持久层-----事务管理

对于J2EE 应用程序而言,事务的处理一般有两种模式:
1. 依赖特定事务资源的事务处理
这是应用开发中最常见的模式,即通过特定资源提供的事务机制进行事务管理。
如通过JDBC、JTA 的rollback、commit方法;Hibernate Transaction 的
rollback、commit方法等。这种方法大家已经相当熟悉。
2. 依赖容器的参数化事务管理
通过容器提供的集约式参数化事务机制,实现事务的外部管理,如EJB 中的事
务管理模式。
如,下面的EJB事务定义中,将SessionBean MySession的doService方
法定义为Required。
也就是说,当MySession.doServer 方法被某个线程调用时,容器将此线程
纳入事务管理容器,方法调用过程中如果发生异常,当前事务将被容器自动回
滚,如果方法正常结束,则容器将自动提交当前事务。
<container-transaction >
<method >
<ejb-name>MySession</ejb-name>
<method-intf>Remote</method-intf>
<method-name>doService</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
容器管理的参数化事务为程序开发提供了相当的灵活性,同时因为将事务委
托给容器进行管理,应用逻辑中无需再编写事务代码,大大节省了代码量(特
别是针对需要同时操作多个事务资源的应用),从而提高了生产率。
然而,使用EJB 事务管理的代价相当高昂,撇开EJB 容器不菲的价格,EJB
的学习成本,部署、迁移、维护难度,以及容器本身带来的性能开销(这往往
意味着需要更高的硬件配置)都给我们带来了相当的困惑。此时事务管理所带
来的优势往往还不能抵消上面这些负面影响。
Spring事务管理能给我们带来什么?
对于传统的基于特定事务资源的事务处理而言(如基于JDBC 的数据库访问),
Spring并不会对其产生什么影响,我们照样可以成功编写并运行这样的代码。同时,
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
Spring还提供了一些辅助类可供我们选择使用,这些辅助类简化了传统的数据库操作
流程,在一定程度上节省了工作量,提高了编码效率。
对于依赖容器的参数化事务管理而言,Spring 则表现出了极大的价值。Spring
本身也是一个容器,只是相对EJB容器而言,Spring显得更为轻便小巧。我们无需付
出其他方面的代价,即可通过Spring实现基于容器的事务管理(本质上来讲,Spring
的事务管理是基于动态AOP)。
下面这段xml配置片断展示了Spring中的事务设定方式:
<beans>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>org.gjt.mm.mysql.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost/sample</value>
</property>
<property name="username">
<value>user</value>
</property>
<property name="password">
<value>mypass</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTr
ansactionManager">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
 
<bean id="userDAOProxy"
class="org.springframework.transaction.interceptor.Tran
sactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref local="userDAO" />
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="get*">
PROPAGATION_REQUIRED,readOnly
</prop>
</props>
</property>
</bean>
</beans>
配置中包含了dataSource,transactionManager 等资源定义。这些资源都为
一个名为userDAOProxy 的TransactionProxyFactoryBean 服务, 而
userDAOProxy 则对包含实际数据逻辑的userDAO进行了事务性封装。
可以看到,在userDAOProxy 的"transactionAttributes"属性中,我们定义了
针对userDAO 的事务策略,即将所有名称以insert 开始的方法(如
UserDAO.insertUser方法)纳入事务管理范围。如果此方法中抛出异常,则Spring
将当前事务回滚,如果方法正常结束,则提交事务

而对所有名称以get 开始的方法(如UserDAO.getUser 方法)则以只读的事务
处理机制进行处理。(设为只读型事务,可以使持久层尝试对数据操作进行优化,如对
于只读事务Hibernate将不执行flush操作,而某些数据库连接池和JDBC 驱动也

只读型操作进行了特别优化。)
结合上面这段申明带来的感性认知,看看Spring 的事务管理机制与EJB 中事务
管理有何不同,或者有何优势。
这里自然有许多方面可以比较,不过,笔者认为其中
最为关键的两点是:
1. Spring可以将任意Java Class 纳入事务管理
这里的UserDAO只是我们编写的一个普通Java Class,其中包含了一些
基本的数据应用逻辑。通过Spring,我们即可简单的实现事务的可配置
化。也就是说,我们可以随意为某个类的某个方法指定事务管理机制。
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
与之对比,如果使用EJB容器提供的事务管理功能,我们不得不按照EJB
规范编将UserDAO 进行改造,将其转换为一个标准的EJB。
2. Spring事务管理并不依赖特定的事务资源。
EJB 容器必须依赖于JTA 提供事务支持。而Spring 的事务管理则支持
JDBC、JTA 等多种事务资源。这为我们提供了更多的选择,从而也使得
我们的系统部署更加灵活。
对Spring事务管理机制进行简单分析之后,我们将结合持久层封装的具体事务应用机
制,对Spring中的事务管理进行更具实效的探讨。

=====================================

JdbcTemplate与事务
上例中的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. 参数化配置的事务管理
下面就这两种方式进行介绍。
u 代码控制的事务管理
首先,进行以下配置,假设配置文件为(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 dhcp组件提供的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 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类的编写似乎也有悖于日常的编程
习惯(虽然笔者觉得这一方法比较有趣,因为它巧妙的解决了笔者在早期自行开发数据访问
模板中曾经遇到的问题)。

u 参数化配置的事务管理
在上面的Application-Context.xml增加一个事务代理(UserDAOProxy)配置,同时,
由于事务由容器管理,UserDAO不再需要TransactionManager设定,将其移除:
<bean id="UserDAOProxy"
class="org.springframework.transaction.interceptor.Transac
tionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref local="userDAO" />
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>

上面的配置中,UserDAOProxy节点配置了一个针对userDAO bean的事务代理(由
target属性指定)。
通过transactionAttributes属性,我们指定了事务的管理策略,即对所有以insert
开头的方法进行事务管理,如果被管理方法抛出异常,则自动回滚方法中的事务,如果成功
执行,则在方法完成之后进行事务提交。另
一方面,对于其他方法(通过通配符*表示),
则进行只读事务管理,以获得更好的性能。
与之对应,UserDAO.insertUser的代码修改如下:
public void insertUser(RegisterInfo regInfo) {
JdbcTemplate jt = new JdbcTemplate(executeTestSource());
jt.update("insert into users (username) values ('xiaxin');");
jt.update("insert into users (id,username) values (2,'erica');");
}
测试代码修改如下:
InputStream is = new FileInputStream("Application-Context.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
//注意这里须通过代理Bean"userDAOProxy"获得引用,而不是直接getBean(“userDAO”)
//此外这里还存在一个有关强制转型的潜在问题,请参见Hibernate in Spring一节后
//关于强制转型的补充描述。
UserDAO userDAO = (UserDAO) factory.getBean("userDAOProxy");

userDAO.insertUser();
可以看到,insertUser变得非常简洁。数据逻辑清晰可见,对比前面代码控制的事务
管理,以及传统的JDBC操作,相信大家会有一些霍然开朗的感觉。
细心的读者会说,这只不过将代码转移到了配置文件,并没有减少太多的工作量。这点
区别也许并不重要,从应用维护的角度而言,配置化的事务管理显然更具优势。何况,实际
开发中,如果前期设计细致,方法的事务特性确定之后一般不会发生大的变动,之后频繁的
维护过程中,我们只需面对代码中的数据逻辑即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值