Spring.NET的中间数据层(Middle Tier Data Access)——事务管理(Transaction management)
简介
Spring.NET为事务管理提供了一个持久化抽象(consistent abstraction ),其优点如下:
- 为不同事务API,例如ADO.NET,Enterprise Services,System.Transactions和NHibernate,提供了一个持久化编程模型。
- 为以上数据获取技术的声明式事务管理(declarative transaction management) 提供支持。
- 为可编程事务管理(programmatic transaction management)提供一个简便的API。
- 集成了Spring的高级持久化集成API,例如AdoTemplate。
这个章节分成几个小节,每个小节都详细介绍了Spring Framework的事务支持技术的技术或者价值。这个章节主要介绍的是一些关于如何最好地实现事务管理的讨论。
- 第一部分叫做动机(Motivations ),介绍了为什么不使用System.Transactions或者其他的数据获取技术的事务API,而选择使用Spring Framework的事务支持。
- 第二部分叫做概述(Abstractions ),介绍了核心类以及如何配置它们。
- 第三部分叫做声明式事务管理(Declarative transaction management),介绍了Spring Framework如何支持声明式事务管理。
- 第四部分叫做可编程事务管理(Programmatic transaction management),介绍了Spring Framework如何支持编程式事务管理。
动机
数据获取技术是一个很广泛的概念,为了实现事务管理在.NET BCL 中存在三种API,分别是ADO.NET, Enterprise Services, and System.Transactions。其他的数据获取技术,例如ORM(object relational mappers )和结果-集合映射库(result-set mapping libraries)也有自己的事务管理API。也就是说,你的事务管理要使用特定的API来实现,因此你必须在开始编码之前就要确定你要用哪种API。那么有了新的需求需要改变你事务管理方式实现的时候,重构就会很麻烦。使用Spring 的事务管理API就可以让你使用同样的API来实现各种不同的数据获取技术。这样,我们主要通过修改简单的配置文件或者少量的实现代码,而不是去翻新整个项目来改变当前的事务管理实现。
一步步地完成各种配置是大家广泛认为最好的建立数据获取方式。Martin Fowler的《企业应用架构模式》很好地介绍了如何获取数据,以及这些方式如何在真实世界中广泛使用。其中,一个广泛使用的方式是在你的架构中引入一个数据获取层(data access layer)。数据获取层的作用范围只限于数据获取,仅关心如何在不同的数据获取技术和数据库之间提供轻便性。一个简单的数据获取层除了支持CRUD的数据获取对象(data access objects,DAOs)之外,不会存在其他的业务逻辑。业务逻辑主要包含在业务逻辑层中,在业务逻辑层中,业务逻辑会调用一个或多个DAO来实现终端用户需要的一些功能。
为了实现终端用户“不全则无”的事务处理理念,事务上下文主要受到业务逻辑层控制(或者其他更高级的层)。在这样一个常见的场景中,一个很重要的实现细节是如何使DAO能够响应其他层的“外部”事务。一个简单的DAO能够实现各自的数据库连接和事务管理,却不支持多个DAO数据库操作的事务,因为DAO仅关心他们自身的事务/资源(transaction/ resource)管理。因此,需要有一个能够将业务逻辑层的连接/事务(connection/transaction)传送到DAO的方法。实现方式有很多,最简单粗暴的方式就是直接把连接/事务对象当作参数传到你的DAO中。还可以将连接/事务对储存在本地数据库中。无论你使用哪种方法,如果你使用的是ADO.NET,你都必须搭建一些基础架构去实现。
等等,Enterprise Services 难道没有任何解决这个问题的方法吗,或者System.Transactions里面的函数呢?答案是有的,但跟没有一样。在一个跨越多个DAO的事务上下文中,Enterprise Services让你使用原始的ADO.NET API去完成事务管理。而Enterprise Services 的缺点是它总是通过微软分布式事务协调器(Microsoft Distributed Transaction Coordinator ,MS-DTC)的分布式事务管理功能,对于大多数应用程序来说,仅仅使用这东西作为事务管理方式性能要比本地ADO.NET 事务要低很多。
使用System.Transactions 名称空间下'using TransactionScope' 也存在相似的问题。TransactionScope 的目的是在一个using的声明中定义一个事务范围(transaction scope )。如果只有一个事务,using 块中的原始ADO.NET代码接下来会变成一个基于本地ADO.NET的事务。 当其他的事务被检测到的时候,System.Transactions的神奇能力就会把本地 ADO.NET的事务转化成分布式事务。这种操作叫做Promotable Single Phase Enlistment(PSPE)。然而,需要注意的是如果你使用同一个数据库连接字段来开启另一个IDbConnection 对象的数据库连接,这样做回导致本地事务转化成分布式事务。因此,如果你的DAO只是在执行他们本地的连接管理操作,它会莫名其妙地变成分布式事务。为了避免这样的问题产生,你必须传一个连接对象到你的DAO。同样要注意的是,很多数据库还不支持PSPE,因此尽管你只用一个数据库仍然使用的是分布式事务管理方式。
接下来是关于使用声明式事务管理。
Last but not least is the ability to use declarative transaction management. Not many topics in database transactionland give developers as much 'bang-for-the-buck' as declarative transactions since the noisy tedious bits of transactional API code in your application are pushed to the edges, usually in the form of class/method attributes. Only Enterprise Services offers this feature in the BCL. Spring fills the gap - it provides declarative transaction management if you are using local ADO.NET or System.Transactions (the most popular) or other data access technologies. Enterprise Services is not without it small warts as well, such as the need to separate your query/ retrieve operations from your create/update/delete operations if you want to use different isolation levels since declarative transaction metadata can only be applied at the class level. Nevertheless, all in all, Enterprise Services, in particular with the new 'Services Without Components' implementation for XP SP2/Server 2003, and hosted within the same process as your application code is as good as it gets out of the .NET box. Despite these positive points, it hasn't gained a significant mindshare in the development community.
Spring's transaction support aims to relieve these 'pain-points' using the data access technologies within the BCL - and for other third party data access technologies as well. It provides declarative transaction management with a configurable means to obtain transaction option metadata - out of the box attributes and XML within Spring's IoC configuration file are supported.
最后,Spring的事务支持让你能够在一个事务中使用多种数据获取技术——例如ADO.NET 和NHibernate。
经过这些婆婆妈妈的介绍之后,让我们进入正题。
重点概述
Spring事务管理的重要概念是事务策略(transaction strategy)。Spring.Transaction.IPlatformTransactionManager 接口定义了事务策略,如下所示:
public interface IPlatformTransactionManager { ITransactionStatus GetTransaction( ITransactionDefinition definition ); void Commit( ITransactionStatus transactionStatus ); void Rollback( ITransactionStatus transactionStatus ); }
这个是一个主要的“服务提供接口”(Service Provider Interface,SPI),尽管它可以以编程的方式使用。注意它贯彻了Spring Framework的理念,IPlatformTransactionManager 是一个接口,因此可以很容易地被更换或者修改。IPlatformTransactionManager的实现就和其他的IoC容器中的对象一样。主要提供了下面的实现:
- AdoPlatformTransactionManager —基于ADO.NET的本地事务
- ServiceDomainPlatformTransactionManager —Enterprise Services的分布式事务管理
- TxScopePlatformTransactionManager —System.Transactions的本地/分布式事务
- HibernatePlatformTransactionManager 使用NHibernate 或者混合使用ADO.NET/NHibernate的本地事务
封装之下包含以下的API。对于AdoPlatformTransactionManager:Transaction.Begin(), Commit(), Rollback()。ServiceDomainPlatformTransactionManager 使用了'Services without Components' 的更新方式,因此你的对象不要继承自ServicedComponent 或者需要直接调用Enterprise Services的 API: ServiceDomain.Enter(), Leave; ContextUtil.SetAbort()。TxScopePlatformTransactionManager 调用:new TransactionScope(); .Complete(), Dispose(), Transaction.Current.Rollback()。每个事务管理器可以配置他们的属性来指定各自的数据获取技术。可以参考API文档来获得更多的信息,但是这里的例子应该可以为你提供一个很好的开始基础。HibernatePlatformTransactionManager 会在下面的小节中详细介绍。
GetTransaction(..) 会根据一个定义ITransactionDefinition 的参数返回一个ITransactionStatus 对象。返回的ITransactionStatus 对象代表一个新的或者现存的事务(如果当前的调用栈中存在对应的事务的话——
ITransactionDefinition 接口主要定义了:
- 独立性(Isolation):这个事务和其他事务的独立性。例如,这个事务能不能共享其他没有提交的更新操作的数据。
- 传播(Propagation):一般来说,事务中的代码都会在这个事务中执行。然而,尽管在一个事务上下文已经存在的情况下,也有多种选择可以让你定义一个事务方法的执行的行为:例如,事务方法简单地在已经存在的事务里面运行(最常见的例子);或者挂起已经存在的事务,然后再新建一个事务。
- 超时(Timeout):事务的超时时长(超过之后会自动回滚) 。
- 只读状态(Read-only status):一个只读的事务,在使用的时候不会修改任何数据的属性。只读事务主要对某些情况进行优化(例如使用NHibernate的时候)。
这些配置反应了标准的事务概念。如果需要的话,请参考讨论事务独立程度的资源和其他的关键事务概念,因为理解折现关键的概念对使用Spring Framework 或者其他的事务管理解决方案很重要。
不管你是使用声明式事务管理还是可编程事务管理,定义正确的IPlatformTransactionManager 接口实现都是很重要的。在当前比较流行的Spring风格中,这些重要的定义都是通过依赖注入(Dependency Injection)来实现的。
IPlatformTransactionManager 接口的实现通常需要对他们工作环境,ADO.NET, NHibernate等等有一定程度上的了解。下面的例子展示了一个标准的基于ADO.NET 的IPlatformTransactionManager 实现。
首先,我们必须定义一个Spring IDbProvider ,再通过引用IDbProvider,使用Spring的AdoPlatformTransactionManager。关于更多的IDbProvider 的信息,关注下一个章节。
<objects xmlns='http://www.springframework.net' xmlns:db="http://www.springframework.net/database"> <db:provider id="DbProvider" provider="SqlServer-1.1" connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/> <object id="TransactionManager" type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> . . . other object definitions . . . </objects>
我们也可以使用基于System.Transactions的事务管理,就像下面展示的那样:
<object id="TransactionManager" type="Spring.Data.Core.TxScopeTransactionManager, Spring.Data"> </object
在ORM事务管理章节也同样定义了类似的HibernateTransactionManager。
需要注意的是,在这些例子中,应用程序的代码都不需要改动,因为在使用这些战略模式(strategy pattern)的时候,依赖注入式一个完美的伙伴。我们现在可以仅仅通过修改配置文件来改变整个事务管理,尽管这个改变意味着从局部到全局,或者相反,从全局到局部。
事务中的资源同步
应用程序代码是如何通过不同的事务管理器参与资源(例如,连接/事务/session)的生成/重用/清理呢?有两个方法—一个高级方法和一个低级方法。
高级同步方法
比较推荐的方式是Spring的高级持久化集成API。这些API不需要替换本地的API,但是会在内部处理生成,重用,清理和可选的事务同步(例如事件通知),以及异常的映射等等。因此用户在使用数据获取的代码的时候就不需要关系这些实现细节,可以专注于非模板化的持久化逻辑。总的来说,同样的控制反转的方式可以使用在所有的持久化API上面。这些API包含回调方法或者委托来通知相应的资源已经可以使用。例如,a DbCommand with its Connection and Transaction properties set based on the transaction option metadata. 这些类使用模板机制,例如AdoTemplate 和 HibernateTemplate。Convenient 'one-liner' helper methods in these template classes build upon the core callback/IoC design by providing specific implementations of the callback interface.(真心看不懂到底想表达什么,讲真,这个文档挺啰嗦的,而且写得对外国人很不友好。)
低级同步方法
可以用一个工具类来获得一个能够感知事务调用上下文调用的“连接/事务”对。ConnectionUtils 包含静态方法GetConnectionTxPair(IDbProvider provider)来实现这个功能。
public class LowLevelIntegration { // Spring's IDbProvider abstraction private IDbProvider dbProvider; public IDbProvider dbProvider { set { dbProvider = value; } } public void DoWork() { ConnectionTxPair connTxPair = ConnectionUtils.GetConnectionTxPair(dbProvider); //Use some data access library that allows you to pass in the transaction DbWrapper dbWrapper = new DbWrapper(); string cmdText = ... // some command text dbWrapper.ExecuteNonQuery(cmdText, connTxPair.Transaction); } }
声明式事务管理
大多Spring 的用户会选择声明式事务管理方式。因为这种方式对应用程序的代码影响最小,所以是非侵入式(non-invasive)轻量级容器的实现目标一致。
Spring 的声明式事务管理可以和Spring的AOP一起使用,然而Spring的事务切面的使用比较模板化,因此我们不必太需要知道AOP的概念就可以使用相关的事务管理功能了。
为了在每个单独方法的层面上确定事务的行为。在每个事务上下文中都可以调用SetRollbackOnly() 方法来标记这个事务的回滚。一些Spring声明式事务的重点主要包括:
- 声明式事务管理可以在任何环境下工作。只要改变配置就可以和ADO.NET, System.Transactions, NHibernate 等共同工作。
- 声明式事务可以在任何类中应用,而不仅仅是那些继承了ServicedComponent 或者其他基础结构相关的类。
- 声明式的回滚规则。回滚规则可以被声明的方式控制,并且允许根据事务上下文中的一些特定的错误来执行回滚。
- Spring提供了一个自定义事务行为的机会:使用AOP。例如如果你可以插入一些自定义回滚行为。你也可以在现有的事务通知的基础上添加任意的通知。
- Spring不支持远程调用的propagation 。
回滚规则的概念是很重要的:它让我们能够定义何种错误会造成自动回滚。我们可以在配置文件中定义而不是在业务代码中。因此,尽管你可以在ITransactionStatus对象中调用SetRollbackOnly() 方法来将现在的事务回滚,然而在更多的情况下,你可以定义某种规则来让你的某种应用程序错误一定会导致回滚。这种做法的一个优点就是你的业务对象不要依赖事务功能基础模块。例如,不需要引入任何的Spring 事务API或者其他的Spring API。然而如果要通过编程式地回滚事务,就需要要调用框架方法:
TransactionInterceptor.CurrentTransactionStatus.SetRollbackOnly()
理解Spring的声明式事务管理的实现
除非你知道它所有工作原理,不然的话,只是简单地告诉你用[Transaction]特性去标记你的类,或者是在配置文件中添加<tx:attribute-driven/>字段就想让你了解整个事务管理是如何控制,那是远远不够的。这一个小节主要解释Spring Framework的声明式事务管理架构如何工作。
掌握Spring Framework的声明式事务管理最重要的是要明白它支持AOP代理,并且事务通知是使用元数据驱动(XML或者特性标签)。这种方式使用一个事务拦截器和一个对应的IPlatformTransactionManager 实现来完成特定方法调用时候的环绕通知。
理论上,在一个事务代理中调用一个方法是这样的:
声明式事务管理实现的例子
仔细思考一下下面的接口,这么做是为了传达相应的概念给你而不是让你纠结于领域细节。
ITestObjectManager 是一个简单的实现,这个实现包含两个DAO的调用。很明显从业务逻辑层层面来说这个service太简单了,它的接口如下:
public interface ITestObjectManager { void SaveTwoTestObjects(TestObject to1, TestObject to2); void DeleteTwoTestObjects(string name1, string name2); }
ITestObjectManager 的实现如下:
public class TestObjectManager : ITestObjectManager { // Fields/Properties ommited [Transaction] public void SaveTwoTestObjects(TestObject to1, TestObject to2) { TestObjectDao.Create(to1.Name, to1.Age); TestObjectDao.Create(to2.Name, to1.Age); } [Transaction] public void DeleteTwoTestObjects(string name1, string name2) { TestObjectDao.Delete(name1); TestObjectDao.Delete(name2); } }
要注意的是方法上的事务标签。其他的配置选项例如isolation也可以在这个标签中设置,但是例子中的特性用的是默认配置选项。然而要注意的是一个特性标签是不足以控制整个事务行为的——特性标签只是一个简单的元数据,能被事务特性感知的东西获取以配置相应的对象来完成整个事务行为。
TestObjectDao 属性有一些基础的增删改查和找到领域模型TestObject的功能。TestObject 有一些基本的属性例如姓名和年龄。
public interface ITestObjectDao { void Create(string name, int age); void Update(TestObject to); void Delete(string name); TestObject FindByName(string name); IList FindAll(); }
增加和删除方法的实现如下。需要注意的是这些实现使用的是AdoTemplate类(会在后文详细讨论)
public class TestObjectDao : AdoDaoSupport, ITestObjectDao { public void Create(string name, int age) { AdoTemplate.ExecuteNonQuery(CommandType.Text, String.Format("insert into TestObjects(Age, Name) VALUES ({0}, '{1}')", age, name)); } public void Delete(string name) { AdoTemplate.ExecuteNonQuery(CommandType.Text, String.Format("delete from TestObjects where Name = '{0}'", name)); } }
TestObjectManager的DAO使用了依赖注入技术。客户端代码,在这个例子中直接访问Spring IoC容器以获得ITestObjectManager的实现,会得到一个使用特性标签配置的事务代理。需要注意的是ITestObjectManager 可能是某些更高层中的对象的依赖注入,例如一个网络服务层。
客户端的调用代码如下
IApplicationContext ctx = new XmlApplicationContext("assembly://Spring.Data.Integration.Tests/Spring.Data/ autoDeclarativeServices.xml"); ITestObjectManager mgr = ctx["testObjectManager"] as ITestObjectManager; TestObject to1 = new TestObject(); to1.Name = "Jack"; to1.Age = 7; TestObject to2 = new TestObject(); to2.Name = "Jill"; to2.Age = 8; mgr.SaveTwoTestObjects(to1, to2); mgr.DeleteTwoTestObjects("Jack", "Jill");
DAO的对象定义和管理类的配置如下
<objects xmlns='http://www.springframework.net' xmlns:db="http://www.springframework.net/database"> <db:provider id="DbProvider" provider="SqlServer-1.1" connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/> <object id="transactionManager" type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> <object id="adoTemplate" type="Spring.Data.AdoTemplate, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> <object id="testObjectDao" type="Spring.Data.TestObjectDao, Spring.Data.Integration.Tests"> <property name="AdoTemplate" ref="adoTemplate"/> </object> <!-- The object that performs multiple data access operations --> <object id="testObjectManager" type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests"> <property name="TestObjectDao" ref="testObjectDao"/> </object> </objects>
这是一个标准的Spring配置并且提供了灵活的连接字符串的参数化和DAO实体的便利转换。
以下小节会展示如何使用Spring的事务名称空间来配置声明式事务
使用transaction名称空间来实现声明式事务
Spring提供了一个可以自定义的XML schema来简化声明式事务管理的配置。如果你像使用基于特性的事务管理,你要先为你的事务名称空间注册对应的名称空间解析器。这个可以在应用程序配置文件中定义,如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="spring"> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" /> <!-- other spring config sections like context, typeAliases, etc not shown for brevity --> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" /> <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" /> <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" /> </parsers> </spring> </configSections>
除了使用前面介绍的XML配置文件(declarativeServices.xml )之外,你可以可以使用下面的方式。
<objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.net/tx" xmlns:db="http://www.springframework.net/database" xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/schema/objects/ spring-objects.xsd http://www.springframework.net/schema/tx http://www.springframework.net/schema/tx/spring-tx-1.1.xsd" http://www.springframework.net/schema/db http://www.springframework.net/schema/db/spring database.xsd"> <db:provider id="DbProvider" provider="SqlServer-1.1" connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/> <object id="transactionManager" type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> <object id="adoTemplate" type="Spring.Data.AdoTemplate, Spring.Data"> <property name="DbProvider" ref="DbProvider"/> </object> <object id="testObjectDao" type="Spring.Data.TestObjectDao, Spring.Data.Integration.Tests"> <property name="AdoTemplate" ref="adoTemplate"/> </object> <!-- The object that performs multiple data access operations --> <object id="testObjectManager" type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests"> <property name="TestObjectDao" ref="testObjectDao"/> </object> <tx:attribute-driven transaction-manager="transactionManager"/> </objects>
<tx:attribute-driven/>标签的各种配置选项总结如下:
表格: <tx:annotation-driven/> 设置
属性 | 是否必须 | 默认值 | 介绍 |
transaction-manager | 否 | transactionManager |
要使用的事务管理对象的名称。仅仅在事务管理对象的名称不是transactionManager的时候是必须指定的,和上文中的例子一样。 |
proxy-target-type | 否 | 控制应用[Transaction]特性的对象上生成的事务代理的类型。如果这个特性设置成true,这个基于类定义的代理就会被生成。代理继承自目标类,然而对目标的调用仍然通过是代理对象。如果这个特性设置成false或者置空,那么一个基于代理的并且你只能把代理设置到接口的实现上。(可以参见“代理机制”获得更多的不同类型的代理的详细例子)
(proxy inherits from target class, however calls are still delegated to target object via composition. This allows for casting to base class. If "proxy-targettype" is "false" or if the attribute is omitted, then a pure composition based proxy is created and you can only cast the proxy to implemented interfaces. (See the section entitled Section 13.6, “Proxying mechanisms” for a detailed examination of the different proxy types.) |
|
order | 否 | 定义将要应用[Transaction]特性的对象上的通知顺序。更多关于AOP通知顺序的信息可以在AOP章节中查找(查看“通知顺序”章节)。要注意如果没有定义任何顺序,就会根据AOP子系统的默认顺序进行。 |
你也可以通过使用<tx:advice>来定义你想要应用的事务语法。和使用事务特性的情况不一样,你可以定义例如propagation 或者isolation 程度和methods for which that metadata applies external to the code 。<tx:advice>在解析的时候会生成ITransactionAttributeSource 的实现实例。如果在上面的例子中使用<tx:advice>而不是<tx:attribute-driven/>的话,就比如这样
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="Save*"/> <tx:method name="Delete*"/> </tx:attributes> </tx:advice>
这个意味着所有以Save或者Delete开头的方法就会和事务元数据的默认配置关联。默认数据会在下文列出来。
下面是一个使用<tx:method/>的例子:
<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> object below) --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="Get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice>
<tx:advice/> 的含义是“所有的以‘Get’开头的方法会在一个只读事务上下文中执行,并且其他所有方法将会用默认的事务语法执行”。标签中的“'transactionmanager' ”属性被设置成了PlatformTransactionManager 对象的名称,这个对象会驱动整个事务(在'transactionManager' 对象的例子中)。
你也可以使用<aop:advisor>把一个切入点和上文定义的通知绑定起来,就像这样:
<object id="serviceOperation" type="Spring.Aop.Support.SdkRegularExpressionMethodPointcut, Spring.Aop"> <property name="pattern" value="Spring.TxQuickStart.Services.*"/> </object> <aop:config> <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/> </aop:config>
假设服务层类TestObjectManager是在Spring.TxQuickStart.Services名称空间下。<aop:config/> 保证这个被txAdvice定义的事务通知能够在正确的时间点执行。首先我们定义了一个切入点匹配所有Spring.TxQuickStart.Services 名称空间下的类(你可以通过正则表达式来更详细地配置)。然后我们使用一个通知器将这个切入点和txAdvice关联起来。这个例子中,配置结果意味着执行'SaveTwoTestObjects' and 'DeleteTwoTestObject'的时候,'txAdvice' 通知就会被执行。
各种各样的事务配置都能使用<tx:advice/>标签来配置。默认的<tx:advice/>在下文中列出,与你使用事务特性的时候是一样的。
- propagation 的设置是 TransactionPropagation.Required
- isolation 程度的设置是IsolationLevel.ReadCommitted
- 事务是可读可写的
- 事务超时设置沿用底层事务系统的默认值,在不支持超时的时候就没有
- EnterpriseServicesInteropOption(仅适用于.NET 2.0 和 TxScopeTransactionManager ) ——通过System.Transactions 生成或者COM+生成的事务配置选项
- 所有的异常都会导致回滚
这些默认设置都是可以修改的,<tx:advice/> 和<tx:attributes/>中需要的<tx:method/>标签都总结如下:
表格: <tx:method/> 的设置
属性 |
是否必须 |
默认值 |
描述 |
name |
是 |
|
事务属性关联的方法的名称。通配符(*)可以用于将相同的事务特性配置与一组方法关联; 例如名称类似’Get*’,’Handle*’,’On*Event’等等的方法。 |
propagation |
否 |
Required |
事务传播行为 |
isolation |
否 |
ReadCommitted |
事务隔离等级 |
timeout |
否 |
-1 |
事务超时(秒) |
read-only |
否 |
fale |
事务只读属性 |
EnterpriseServicesInteropOption |
否 |
None |
|
rollback-for |
否 |
|
触发回滚的异常,使用逗号分隔,例如,'MyProduct.MyBusinessException,ValidationException' |
no-rollback-for |
否 |
|
不会触发回滚的异常,使用逗号分隔,例如,'MyProduct.MyBusinessException,ValidationException' |
使用特性完成声明式事务
事务特性是指定类或者方法包含的特定事务语法的元数据 ,默认的事务特性设置是这样的:
- propagation 的设置是 TransactionPropagation.Required
- isolation 程度的设置是IsolationLevel.ReadCommitted
- 事务是可读可写的
- 事务超时设置沿用底层事务系统的默认值,在不支持超时的时候就没有
- EnterpriseServicesInteropOption(仅适用于.NET 2.0 和 TxScopeTransactionManager ) ——通过System.Transactions 生成或者COM+生成的事务配置选项
- 所有的异常都会导致回滚
默认配置理所当然是可以修改的,事务特性的各种性质总结如下:
属性 | 类型 | 描述 |
TransactionPropagation |
枚举,Spring.Transaction.TransactionPropagation |
propagation设置. (Required, Supports, Mandatory, RequiresNew, NotSupported, Never, Nested) |
Isolation | System.Data.IsolationLevel | isolation level设置 |
ReadOnly | 布尔 | 是可读可写的还是只读的事务 |
EnterpriseServicesInteropOption | 枚举,System.Transactions.EnterpriseServicesInteropOption | 可选的和COM+事务的互操作配置(仅适用于.NET 2.0 和 TxScopeTransactionManager ) |
Timeout | 整型(秒) | 事务超时设置 |
RollbackFor | type对象数组 | 一个一定会造成回滚的异常对象数组 |
NoRollbackFor | type对象数组 | 一个一定不会造成回滚的异常对象数组 |
需要注意的是,当一个真实的嵌套事务产生的时候,如果把TransactionPropagation 设置成Nested的话,例如不是在使用Nested propagation 而是在没有任何nested调用的情况下,会抛出NestedTransactionNotSupportedException 异常。这个问题会在能够支持nested事务的SqlServer和Oracle 的Spring 1.2的发布版中解决。还需注意的是,isolation称固定的改变仍然会在Spring1.2发布版中解决。
Note that setting the TransactionPropagation to Nested will throw a NestedTransactionNotSupportedException in a case where an actual nested transaction occurs, i.e. not in the case of applying the Nested propagation but in fact no nested calls are made. This will be fixed for the Spring 1.2 release for SqlServer and Oracle which support nested transactions. Also note, that changing of isolation levels on a per-method basis is also scheduled for the Spring 1.2 release since it requires detailed command text metadata for each dbprovider. Please check the forums for news on when this feature will be introduced into the nightly builds.
If you specify an exception type for 'NoRollbackFor' the action taken is to commit the work that has been done in the database up to the point where the exception occurred. The exception is still propagated out to the calling code.
The ReadOnly boolean is a hint to the data access technology to enable read-only optimizations. This currently has no effect in Spring's ADO.NET framework. If you would like to enable read-only optimizations in ADO.NET this is generally done via the 'Mode=Read' or 'Mode=Read-Only" options in the connection string. Check your database provider for more information. In the case of NHibernate the flush mode is set to Never when a new Session is created for the transaction.
Throwing exceptions to indicate failure and assuming success is an easier and less invasive programming model than performing the same task Programatically - ContextUtil.MyTransactionVote or TransactionScope.Complete. The rollback options are a means to influence the outcome of the transaction based on the exception type which adds an extra degree of flexibility.
Having any exception trigger a rollback has similar behavior as applying the AutoComplete attribute available when using .NET Enterprise Services. The difference with AutoComplete is that using AutoComplete is also coupled to the lifetime of the ServicedComponent since it sets ContextUtil.DeactivateOnReturn to true. For a stateless DAO layer this is not an issue but it could be in other scenarios. Spring's transactional aspect does not affect the lifetime of your object.
使用自动代理完成声明式事务管理
如果你选择在声明式事务管理中不适用事务名称空间,你可以使用“低级”对象定义来配置声明式事务管理。
if you choose not to use the transaction namespace for declarative transaction management then you can use 'lower level' object definitions to configure declarative transactions. The use of Spring's autoproxy functionality defines criteria to select a collection of objects to create a transactional AOP proxy. There are two AutoProxy classes that you can use, ObjectNameAutoProxyCreator and DefaultAdvisorAutoProxyCreator. If you are using the new transaction namespace support you do not need to configure these objects as a DefaultAdvisorAutoProxyCreator is created 'under the covers' while parsing the transaction namespace elements
使用 ObjectNameAutoProxyCreator生成事务代理
The ObjectNameAutoProxyCreator is useful when you would like to create transactional proxies for many objects. The definitions for the TransactionInterceptor and associated attributes is done once. When you add new objects to your configuration file that need to be proxies you only need to add them to the list of object referenced in the ObjectNameAutoProxyCreator. Here is an example showing its use. Look in the section that use ProxyFactoryObject for the declaration of the transactionInterceptor.
<object name="autoProxyCreator" type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"> <property name="InterceptorNames" value="transactionInterceptor"/> <property name="ObjectNames"> <list> <idref local="testObjectManager"/> </list> </property> </object>
使用 DefaultAdvisorAutoProxyCreator生成事务代理
This is not longer a common way to configure declarative transactions but is discussed in the "Classic Spring" appendiex here.
编程式事务管理
Spring provides two means of programmatic transaction management:
-
Using the
TransactionTemplate
-
Using a
IPlatformTransactionManager
implementation directly
These are located in the Spring.Transaction.Support namespace. If you are going to use programmatic transaction management, the Spring team generally recommends the first approach (i.e. Using the TransactionTemplate
)
使用事务模板
The TransactionTemplate adopts the same approach as other Spring templates such as AdoTemplate
and HibernateTemplate
. It uses a callback approach, to free application code from having to do the boilerplate acquisition and release of resources, and results in code that is intention driven, in that the code that is written focuses solely on what the developer wants to do. Granted that the using construct of System.Transaction alleviates much of this. One key difference with the approach taken with the TransactionTemplate is that a commit is assumed - throwing an exception triggers a rollback instead of using the TransactionScope API to commit or rollback. This also allows for the use of rollback rules, that is a commit can still occur for exceptions of certain types.
Application code that must execute in a transaction context looks like this. You, as an application developer, will write a ITransactionCallback implementation (typically expressed as an anonymous delegate) that will contain all of the code that you need to have execute in the context of a transaction. You will then pass an instance of your custom ITransactionCallback to the Execute(..) method exposed on the TransactionTemplate. Note that the ITransactionCallback
can be used to return a value:
public class SimpleService : IService { private TransactionTemplate transactionTemplate; public SimpleService(IPlatformTransactionManager transactionManager) { AssertUtils.ArgumentNotNull(transactionManager, "transactionManager"); transactionTemplate = new TransactionTemplate(transactionManager); } public object SomeServiceMethod() { return tt.Execute(delegate { UpdateOperation(userId); return ResultOfUpdateOperation2(); }); } }
This code example is specific to .NET 2.0 since it uses anonymous delegates, which provides a particularly elegant means to invoke a callback function as local variables can be referred to inside the delegate, i.e. userId. In this case the ITransactionStatus was not exposed in the delegate (delegate can infer the signature to use), but one could also obtain a reference to the ITransactionStatus instance and set the RollbackOnly property to trigger a rollback - or alternatively throw an exception. This is shown below
tt.Execute(delegate(ITransactionStatus status) { try { UpdateOperation1(); UpdateOperation2(); } catch (SomeBusinessException ex) { status.RollbackOnly = true; } return null; });
If you are using .NET 1.1 then you should provide a normal delegate reference or an instance of a class that implements the ITransactionCallback interface. This is shown below
tt.Execute(new TransactionRollbackTxCallback(amount)); public class TransactionRollbackTxCallback : ITransactionCallback { private decimal amount; public TransactionRollbackTxCallback(decimal amount) { this.amount = amount } public object DoInTransaction(ITransactionStatus status) { adoTemplate.ExecuteNonQuery(CommandType.Text, "insert into dbo.Debits (DebitAmount) VALUES (@amount)", "amount", DbType.Decimal, 0,555); // decide you need to rollback... status.RollbackOnly = true; return null; } }
配置事务
Transaction settings such as the propagation mode, the isolation level, the timeout, and so forth can be set on the TransactionTemplate either programmatically or in configuration. TransactionTemplate instances by default have the default transactional settings. Find below an example of programmatically customizing the transactional settings for a specific TransactionTemplate.
public class SimpleService : IService { private TransactionTemplate transactionTemplate; public SimpleService(IPlatformTransactionManager transactionManager) { AssertUtils.ArgumentNotNull(transactionManager, "transactionManager"); transactionTemplate = new TransactionTemplate(transactionManager); // the transaction settings can be set here explicitly if so desired transactionTemplate.TransactionIsolationLevel = IsolationLevel.ReadUncommitted; transactionTemplate.TransactionTimeout = 30; // and so forth... } . . . }
Find below an example of defining a TransactionTemplate with some custom transactional settings, using Spring XML configuration. The 'sharedTransactionTemplate' can then be injected into as many services as are required.
<object id="sharedTransactionTemplate" type="Spring.Transaction.Support.TransactionTemplate, Spring.Data"> <property name="TransactionIsolationLevel" value="IsolationLevel.ReadUncommitted"/> <property name="TransactionTimeout" value="30"/> </object>
使用 PlatformTransactionManager
You can also use the PlatformTransactionManager directly to manage your transaction. Simply pass the implementation of the PlatformTransactionManager you're using to your object via a object reference through standard Dependency Injection techniques. Then, using the TransactionDefinition and ITransactionStatus objects, you can initiate transactions, rollback and commit.
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.PropagationBehavior = TransactionPropagation.Required; ITransactionStatus status = transactionManager.GetTransaction(def); try { // execute your business logic here } catch (Exception e) { transactionManager.Rollback(status); throw; } transactionManager.Commit(status);
Note that a corresponding 'using TransactionManagerScope' class can be modeled to get similar API usage to System.Transactions TransactionScope.
在编程式事务管理和声明式事务管理中做选择
Programmatic transaction management is usually a good idea only if you have a small number of transactional operations. For example, if you have a web application that require transactions only for certain update operations, you may not want to set up transactional proxies using Spring or any other technology. In this case, using the TransactionTemplate may be a good approach. On the other hand, if your application has numerous transactional operations, declarative transaction management is usually worthwhile. It keeps transaction management out of business logic, and is not difficult to configure in Spring.
事务的生命周期和状态信息
You can query the status of the current Spring managed transaction with the class TransactionSynchronizationManager
. Typical application code should not need to rely on using this class but in some cases it is convenient to receive events around the lifecycle of the transaction, i.e. before committing, after committing. TransactionSynchronizationManager
provides a method to register a callback object that is informed on all significant stages in the transaction lifecycle. Note that you can register for lifecycle call back information for any of the transaction managers you use, be it NHibernate or local ADO.NET transactions.
The method to register a callback with the TransactionSynchronizationManager
is
public static void RegisterSynchronization( ITransactionSynchronization synchronization )
Please refer to the SDK docs for information on other methods in this class.
The ITransactionSynchronization
interface is
public interface ITransactionSynchronization { // Typically used by Spring resource management code void Suspend(); void Resume(); // Transaction lifeycyle callback methods // Typically used by Spring resource management code but maybe useful in certain cases to application code void BeforeCommit( bool readOnly ); void AfterCommit(); void BeforeCompletion(); void AfterCompletion( TransactionSynchronizationStatus status ); }
The TransactionSynchronizationStatus
is an enum with the values Committed, Rolledback, and Unknown.