本章内容
统一中原的过程
和平年代
Spring的事务框架将开发过程中事务管理相关的关注点进行适当的分离,并对这些关注点进行合理的抽象,最终打造了一套使用方便,却功能强大的事务管理“利器”。
通过Spring的事务框架,我们可以按照统一的编程模型来进行事务编程,却不用关心所使用的数据访问技术以及具体要访问什么类型的事务资源。并且,Spring的事务框架与Spring提供的数据访问支持可以紧密结合,更是让我们在事务管理与数据访问之间游刃有余。而最主要的是,结合Spring的AOP框架,Spring的事务框架为我们带来了原来只有CMT才有的声明式事务管理的特殊待遇,却无需绑定到任何的应用服务器上。
其他溢美之词咱就先放一边,还是赶快进入正题吧!
Spring的事务框架设计理念的基本原则是:让事务管理的关注点与数据访问关注点相分离。
当在业务层使用事务的抽象API进行事务界定的时候,不需要关心事务将要加诸于上的事务资源是什么,对不同的事务资源的管理将由相应的框架实现类来操心。
当在数据访问层对可能参与事务的数据资源进行访问的时候,只需要使用相应的数据访问API进行数据访问,不需要关心当前的事务资源如何参与事务或者是否需要参与事务。这同样将由事务框架类来打理。
在以上两个关注点被清晰地分离出来之后,对于我们开发人员来说,唯一需要关心的,就是通过抽象后的事务管理API对当前事务进行界定而已,如下方代码清单所示。
public class FooService {
private PlatformTransactionManagertransactionManager;
public void serviceMethod() {
TrạnsactionDefinition definition = ...;
TransactionStatus txStatus = getTransactionManager().getTransaction(definition);
try {
// dao1.doDataAccess();
// dao2.doDataAccess();
// ...
}
catch(DataAccessException e) {
getTransactionManager().rollback(txStatua);
throw e;
}
catch(OtherNecessaryException e) {
getTransactionManager().rollback(txStatus);
throw e;
}
getTransactionManager().commit(txStatus);
}
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager=transactionManager;
}
}
不管数据访问方式如何变换,事务管理实现也可以岿然不动。只要有了这样一个统一的事务管理编程模型,剩下的声明式事务管理也就很自然地成为锦上添花之作了!从此之后,事务管理就是事务管理,数据访问只关心数据访问,再也不用因为它们之间的纠缠而烦恼。
org.springframework.transaction.PlatformTransactionManager
是Spring事务抽象架构的核心接口,它的主要作用是为应用程序提供事务界定的统一方式。既然事务界定的需要很简单,那么PlatformTransactionManager的定义看起来也不会过于复杂,如下所示:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager是整个事务抽象策略的项层接口,它就好像我们的战略蓝图,而战略的具体实施则将由相应的PlatformTransactionManager实现类来执行。
Spring的事务框架针对不同的数据访问方式以及全局事务场景,提供了相应的PlatformTransactionManager实现类。在每个实现类的职责完成之后,Spring事务框架的“统一大业”就完成了。在深入了解各个PlatformTransactionManager实现类的奥秘之前,不妨先考虑一下,如果让我们来实现一个PlatformTransactionManager,要如何去做?
不妨先以针对JDBC数据访问方式的局部事务管理为例。对于层次划分清晰的应用来说,我们通常都是将事务管理放在Service层,而将数据访问逻辑放在DAO层。这样做的目的是,可以不用因为将事务管理代码放在DAO层,而降低数据访问逻辑的重用性,也可以在Service层根据相应逻辑,来决定提交或者回滚事务。一般的Service对象可能需要在同一个业务方法中调用多个数据访问对象的方法,类似于图19-1这样的情况。
因为JDBC的局部事务控制是由同一个java.sql.Connection
来完成的,所以要保证两个DAO的数据访问方法处于一个事务中,我们就得保证它们使用的是同一个java.sql.Connection
。要做到这一点,通常会采用称为connection-passing
的方式,即为同一个事务中的各个dao的数据访问方法传递当前事务对应的同一个java.sql.Connection
,这样,我们的业务方法以及数据访问方法都得做一定的修改,如图19-2所示。
我们只要把java.sql.Connection
的获取并设置Autocommit状态的代码,以及使用java.sql.Connection
提交事务的代码,重构到原来的开启事务以及提交事务的方法中,针对JDBC的局部事务管理的整合看起来离成功也就是咫尺之遥了。不过,这看起来的咫尺之遥,实际上却依然遥远。
使用这种方式,最致命的一个问题就是,不但事务管理代码无法摆脱java.sql.Connection
的纠缠,而且数据访问对象的定义要绑定到具体的数据访问技术上来。现在是使用JDBC进行数据访问,我们要在数据访问方法中声明对java.sql.Connection
的依赖,那要是使用Hibernate的话,是不是又要声明对Session的依赖呢?显然,这样的做法是不可行的。不过好消息是,传递Connection的理念是对的,只不过,在具体实施过程中,我们所采用的方法有些不对头。
要传递java.sql.Connection
,我们可以将整个事务对应的java.sql.Connection
实例放到统一的一个地方去,无论是谁,要使用该资源,都从这一个地方来获取,这样就解除了事务管理代码和数据访问代码之间通过java.sql.Connection
的“直接”耦合。具体点儿说就是,我们在事务开始之前取得一个java.sql.Connection
,然后将这个Connection绑定到当前的调用线程。之后,数据访问对象在使用Connection进行数据访问的时候,就可以从当前线程上获得这个事务开始的时候绑定的Connection实例。当所有的数据访问对象全部使用这个绑定到当前线程的Connection完成了数据访问工作时,我们就使用这个Connection实例提交或者回滚事务,然后解除它到当前线程的绑定。整个过程如图19-3所示。
这时的java.sql.Connection
就像那大河上的一条船,从启航(事务开始)到航程结束(事务完成)这整个期间,大河沿岸都可以与该船打交道。而至于说是发“木船”还是发“铁轮”,那要根据情况来决定了:发JDBC的船,那就是Connection;发Hibernate的船,那就是Sessin…
假设TransactionResourceManager就是存放java.sql.Connection
(或者其他事务资源)的地方,那么,它看起来将如下方代码清单所示(过多的逻辑检验代码略去)。
public class TransactionResourceManager {
private static ThreadLocal resources = new ThreadLocal();
public static Object getResource() {
return resources.get();
}
public static void bindResource(Object resource) {
resources.set(resource);
}
public static Object unbindResource() {
Object res = getResource();
resources.set(null);
return res;
}
}
对于我们要实现的针对JDBC的PlatformTransactionManager
,只需要在事务开始的时候通过我们的TransactionResourceManager
将java.sql.Connection
绑定到线程,然后在事务结束的时候解除绑定即可(原型代码参照下方代码清单)。
public class JdbcTransactionManager implements PlatformTransactionManager {
private DataSource dataSource;
public JdbcTransactionManager(DataSourcedataSource) {
this.dataSource=dataSource;
}
public TransactionStatus getTransaction(TransactionDefinitiondefinition) throws TransactionException {
Connection connection;
try {
connection = dataSource.getConnection();
TransactionResourceManager.bindResource(connection);
return new DefaultTransactionStatus(connection,true,true,false,true,null);
}
catch(SQLException e) {
throw new CannotCreateTransactionException("can't get connection for tx",e);
}
}
public void rollback(TransactionStatus txStatus) throws TransactionException {
Connection connection = (Connection)TransactionResourceManager.unbindResource();
try {
connection.rollback();
}
catch(SQLExceptione) {
throw new UnexpectedRollbackException("rollback failed with SQLException", e);
}
finally {
try {
connection.close();
} catch(SQLException e) {
// 记录异常信息,但通常不会有进一步有效的处理
}
}
}
public void commit(TransactionStatus txStatus) throws TransactionException {
Connectionconnection = (Connection) TransactionResourceManager.unbindResource();
try {
connection.commit();
}
catch(SQLException e) {
throw new TransactionSystemException("commit failed with SQLException",e);
}
finally {
try {
connection.close();
} catch(SQLException e) {
//记录异常信息,但通常不会有进一步有效的处理
}
}
}
}
因为Connection在事务开始和结束期间都可以通过我们的TransactionResourceManager获得,所以所有的DAO层数据访问对象在使用JDBC进行数据访问的时候,就可以直接从TransactionResourceManager获得数据库连接并进行数据访问。这样就可以保证在整个事务期间,所有的数据访问对象对应的是同一个Connection,如下所示:
public class FooJdbcDao implements IDao {
public void doDataAccess() {
Connection con = (Connection) TransactionResourceManager.getResource();
// ...
}
}
至此,我们完成了PlatformTransactionManager的具体实现类,并解除了它与相应数据访问对象之间通过java.sql.Connection
的直接耦合。进行事务控制的时候,我们只需要为Service对象提供相应的PlatformTransactionManager实现类,Service对象中的事务管理功能就算大功告成了,而不需要关心到底对应的是什么样的事务资源,甚至什么样的数据访问方式。
当然,为了便于你理解Spring抽象层的实现原理,以上的代码实例都是简化后的模型,所以,不要试图将它们应用于生产环境。原型代码永远都是原型代码,要做的事情还有许多,如下所述。
(1)如何保证PlatformTransactionManager的相应方法以正确的顺序被调用?如果哪个方法没有被正确调用,也会造成资源泄漏以及事务管理代码混乱的问题。在稍后介绍使用Spring进行编程事务管理的时候,你将看到Spring是如何解决这个问题的。
(2)数据访问对象的接口定义不会因为最初的connection-passing方式而改变契约了,但是,现在却要强制每个数据访问对象使用TransactionResourceManager来获取数据资源接口。另外,如果当前数据访问对象对应的数据方法不想参与跨越多个数据操作的事务的话,甚至于不想(或不能)使用类似的事务管理支持,是否就意味着无法获得connection进行数据访问了呢?
不知道你是否还记得我们在介绍Spring的数据访问相关内容的时候,曾经提到的org.springframework.jabc.datasource.DataSourceUtils
工具类。当时我们只是强调了DataSourceUtils提供的异常转译能力。实际上,DataSourceUtils最主要的工作是对connection的管理,DataSourceUtils会从类似TransactionResqurceManager的类(Spring中对应org.springframework.transaction.support.TransactionSynchronizationManager
)那里获取Connection资源。如果当前线程之前没有绑定任何connection,那么它就通过数据访问对象的DataSource引用获取新的connection,否则就使用绑定的那个connection。这就是为什么要强调,当我们要使用Spring提供的事务支持的时候,必须通过DataSourceUtils来获取连接。因为它提供了Spring事务管理框架在数据访问层需要提供的基础设施中不可或缺的一部分,而JdbcTemplate等类内部已经使用DataSourceUtils来管理连接了,所以,我们不用操心这些细节。从这里,我们也应可以看出,Spring的事务管理与它的数据访问框架是紧密结合的。
注意:对应Hibermate的SessionFactoryUtils,对应JDO的PersistenceManagerFactoryUtils以及对应其他数据访问技术的Utils类,它们的作用与DataSourceUtils是相似的。除了提供异常转译功能,它们更多地被用于数据访问资源的管理工作,以配合对应的PlatformTransactionManager实现类进行事务管理。
实际上,Spring在实现针对各种数据访问技术的PlatformTransactionManager的时候,要考虑很多东西,不像原型以及提出的几个问题所展示的那么简单。不过,各个实现类的基本思路与原型所展示的是基本吻合的。当我们了解了针对JDBC的PlatformTransactionManager是如何实现的时候,其他的实现类基本上就是平推了。