WCF技术剖析之三十一: WCF事务编程[下篇]
在WCF事务编程模型下,通过服务契约确定事务流转的策略(参阅《上篇》),通过事务绑定实施事务的流转(参阅《中篇》)。但是,对于事务绑定接收到并成功创建的事务来说,服务操作的执行是否需要自动登记到该事务之中,以及服务操作采用怎样的提交方式,这就是服务端自己说了算了。正因为如此,WCF通过服务(操作)行为的形式定义事务的登记和提交(完成)方式。
一、事务的自动登记(Enlistment)与提交(完成)
在OperationBehaviorAttribute特性(其本身是一个操作行为)中定了两个与事务管理相关的属性:TransactionAutoComplete和TransactionScopeRequired。
1: [AttributeUsage(AttributeTargets.Method)]
2: public sealed class OperationBehaviorAttribute : Attribute, IOperationBehavior
3: {
4: //其他成员
5: public bool TransactionScopeRequired { get; set; }
6: public bool TransactionAutoComplete { get; set; }
7: }
如上面的代码所示,这两个属性均为布尔类型,它们代表的含义如下:
- TransactionScopeRequired:表示相应的操作的整个执行是否自动登记到一个事务中。具体来讲,如果客户端流程成功地流入服务端,并被服务端事务绑定成功接收,将该属性设为True以为着整个操作的执行将自动被纳入到流入的事务之中,服务操作将会成为客户端事务的一部分。如果服务端不曾成功接收流入的事务,将该属性设为True意味着操作的执行将会被纳入到一个新创建的事务中。TransactionScopeRequired的默认值为False;
- TransactionAutoComplete:表示如果操作执行过程中没有抛出异常,完成后将自动提交事务(对于最外层的事务)或者向被依赖的事情进行投票(对于嵌套的依赖事务)。TransactionAutoComplete的默认值为True。
如果我们需要将整个操作(而不是操作的一部分)纳入到事务中执行,我们只需要将OperationBehaviorAttribute特性应用到服务类型中的相应的方法之上,并将TransactionScopeRequired属性设为True即可。相面的代码中,我通过应用OperationBehaviorAttribute特性将服务BankingService的Transfer方法定义成事务型操作方法。
1: public class BankingService : IBankingService
2: {
3: [OperationBehavior(TransactionScopeRequired = true)]
4: public void Transfer(string accountFrom, string accountTo, double amount)
5: {
6: //省略实现
7: }
8: }
将TransactionAutoComplete属性设为True(默认就是True)可以使我们需要考虑对本地事务的提交问题。只要执行完最后一句代码尚无异常抛出,则会提交(或完成)事务。但是有时候,我们需要不同的事务提交(完成)策略,比如服务方法中包含一些非事务型操作(比如日记记录),只要保证正常的业务逻辑正常执行就可以提交(完成)事务。在这种情况下,我们可以将该属性设为False,通过调用当前OperationContext的SetTransactionComplete方法即可实现对本地事务的提交。
1: public sealed class OperationContext : IExtensibleObject<OperationContext>
2: {
3: //其他成员
4: public void SetTransactionComplete();
5: }
除了定义在OperationBehaviorAttribute特性中的基于操作的行为,还有一些与事务相关的服务行为,它们定义在我们熟悉的ServiceBehaviorAttribute特性中。
二、事务相关的服务行为
如下面的代码所示, ServiceBehaviorAttribute特性定义了四个与事务相关的属性。其中TransactionIsolationLevel指定事务的隔离级别,默认值为IsolationLevel.Serializable;TransactionTimeout以字符串定义事务的超市时限,WCF运行时会根据指定的字符串创建TimeSpan对象;TransactionAutoCompleteOnSessionClose表示在会话正常结束(没有出现异常)之后是否自动提交或为完成开启的事务,默认值为False;ReleaseServiceInstanceOnTransactionComplete则表示当事务完成之后是否需要将服务实例释放掉,默认为False。
1: [AttributeUsage(AttributeTargets.Class)]
2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
3: {
4: //其他成员
5: public IsolationLevel TransactionIsolationLevel { get; set; }
6: public string TransactionTimeout { get; set; }
7: public bool TransactionAutoCompleteOnSessionClose { get; set; }
8: public bool ReleaseServiceInstanceOnTransactionComplete { get; set; }
9: }
在下面的代码中,通过在BankingService上应用ServiceBehaviorAttribute特性将隔离级别设置成ReadCommitted,超时时限设置成5分钟,并将TransactionAutoCompleteOnSessionClose设置成True。
1: [ServiceBehavior(TransactionIsolationLevel = IsolationLevel.ReadCommitted,
2: TransactionTimeout = "00:05:00",
3: TransactionAutoCompleteOnSessionClose = true)]
4: public class BankingService : IBankingService
5: {
6: //省略成员
7: }
当你通过ServiceBehaviorAttribute特性对上述的四个属性的任一个进行的设置,即使是设置成默认值,如果服务中并不存在一个TransactionScopeRequired属性为True的操作,在进行服务寄宿的时候将会抛出异常。就以上面的设置为例,在BankingService中的唯一的Transfer方法上,并没有通过OperationBehaviorAttribute将TransactionScopeRequired属性为True,在对服务进行寄宿的时候,就会抛出如图1所示的InvalidOperationException异常。
1: [ServiceBehavior(TransactionIsolationLevel = IsolationLevel.ReadCommitted,
2: TransactionTimeout = "00:05:00",
3: TransactionAutoCompleteOnSessionClose = true)]
4: public class BankingService : IBankingService
5: {
6: public void Transfer(string accountFrom, string accountTo, double amount)
7: {
8: //省略实现
9: }
10: }
图1 为不存TransactionScopeRequired操作的服设置在事务相关服务行为导致的异常
通过TransactionTimeout设置的事务超时时限最终会被赋予ChannelDispatcher的同名属性。该属性的默认值为TimeSpan.Zero,在这种情况下,运行时将会采用System.Transactions的默认超时设置。如果设置的TransactionTimeout的值超过了System.Transactions设置的最大超时时限,后者将会自动作为运行时的事务超时时限。相关类型请参考《谈谈分布式事务之三: System.Transactions事务详解[上篇]》和《谈谈分布式事务之三: System.Transactions事务详解[下篇]》
1: public class ChannelDispatcher : ChannelDispatcherBase
2: {
3: //其他成员
4: public TimeSpan TransactionTimeout { get; set; }
5: }
基于事务超时时限的服务行为也可以通过配置的方式指定,对应的配置节为<serviceTimeouts>,你只需按照所需的各式设置transactionTimeout属性值即可。在下面的配置中,我们将BankingService服务的事务超时时限设置成30分钟。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service behaviorConfiguration="transactionBehavior" name="Artech.TransactionalServices.BankingService">
6: <endpoint address="net.tcp://127.0.0.1:3721/bankingservice" binding="netTcpBinding" contract="Artech.TransactionalServices.IBankingService" />
7: </service>
8: </services>
9: <behaviors>
10: <serviceBehaviors>
11: <behavior name="transactionBehavior">
12: <serviceTimeouts transactionTimeout="00:30:00" />
13: </behavior>
14: </serviceBehaviors>
15: </behaviors>
16: </system.serviceModel>
17: </configuration>
在事务流转的场景中,流入的事务和目标服务的事务隔离级别必须一致。也就是说,如果服务调用存在于客户端现有的事务之中,当前客户端事务的隔离级别必须和目标服务具有相同的隔离级别。否则,服务端将会返回相应的Fualt消息并导致客户端抛出异常。同样对于上面我们定义的BankingService(TransactionIsolationLevel = IsolationLevel.ReadCommitted),如果客户端按照下面的方式进行调用,由于客户端事务采用默认的隔离界别Serializable,会抛出如图2所示的ProtocolException异常。
1: using (ChannelFactory<IBankingService> channelFactory = new ChannelFactory<IBankingService>("bankingservice"))
2: {
3: IBankingService bankingservice = channelFactory.CreateChannel();
4: using (TransactionScope transactionScope = new TransactionScope())
5: {
6: bankingservice.Transfer("Foo", "Bar", 1000);
7: transactionScope.Complete();
8: }
9: }
图2 事务隔离级别不一致导致的异常
到此为止,WCF事务编程模型涉及到的三个方面,即服务(操作)契约、绑定和服务(操作)行为就介绍完了。接下来,我们将给出一个完整的例子。
- iOS .pch文件的使用
- CountDownTimer说明及使用
- UiTextField对输入的长度进行限制并提示用户还可输入的长度
- Spring Cloud实战小贴士:turbine如何聚合设置了context-path的hystrix数据
- iOS 查找字符串 相同 子字符串的位置 range
- 自然语言处理指南(第四部分)
- 史上最强Spring mvc入门
- 上边半透明的效果并且显示的是上一页的内容
- Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】
- android自定义view实现公章效果
- ios app url scheme跳转到淘宝商品详情页 唤醒app
- ThreadPoolExecutor运行机制
- Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】
- UIPickView的简单使用
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 详解 Cookie,Session,Token
- SSM 单体框架 - 前端开发:课程和广告模块
- 80 行 Python 代码写个图形计算器
- 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等)
- SSM 单体框架 - 前端开发:用户和权限模块
- Cnitch:一款Docker引擎运行进程权限检测工具
- SAP Spartacus BrowserPlatformLocation的初始化逻辑
- 重启 CentOS 7 系统后的 IP 地址问题
- SSM 单体框架 - 前端开发:用户权限控制,Nginx 和项目部署与发布
- 用 Python 制作飞机大战小游戏
- SSM 单体框架 - 前端开发:视频讲解
- MyBatis 的 `<if test="">` 语句里面使用反单引号的问题
- 30 个Python代码实现的常用功能,精心整理版
- Java 后台开发面试题分享一
- IDEA 报错:no tests were found 和 UnsupportedOperationException