谈谈分布式事务之三: System.Transactions事务详解[下篇]

在前面一篇给出的Transaction的定义中,信息的读者应该看到了一个叫做DepedentClone的方法。该方法对用于创建基于现有Transaction对 象的“依赖事务(DependentTransaction)”。不像可提交事务是一个独立的事务对象,依赖事务依附于现有的某个事务(可能是可提交事 务,也可能是依赖事务)。依赖事务可以帮助我们很容易地编写一些事务型操作,当环境事务不存的时候,可以确保操作在一个独立的事务中执行;当环境事务存在 的时候,则自动加入其中。

一、依赖事务(Dependent Transaction)

依赖事务通过DependentTransaction类型表示,DependentTransaction定义如下。和CommittableTransaction一样,DependentTransaction也是Transaction的子类。既然DependentTransaction依赖于现有的Transaction对象而存在,相当于被依赖事务的子事务,所以无法执行对事务的提交,也自然不会定义Commit方法。但是,DependentTransaction具有一个唯一的方法成员:Complete。调用这个方法意味着向被依赖事务发送通知,表明所有与依赖事务相关的操作已经完成。

   1: [Serializable]
   2: public sealed class DependentTransaction : Transaction
   3: {  
   4:     public void Complete();
   5: }

1、通过DependentTransaction将异步操所纳入现有事务

通过Transaction的 静态属性Current表示的环境事务保存在TLS(Thread Local Storage)中,所以环境事务是基于当前线程的。这就意味着,即使环境事务存在,通过异步调用的操作也不可能自动加入到当前事务之中,因为在异步线程 中感知不到环境事务的存在。在这种情况下,我们需要做的就是手工将当前事务传递到另一个线程中,作为它的环境事务。通过依赖事务我们很容易实现这一点。

DependentTransaction通过Transaction的DependentClone方法创建,该方法具有一个DependentCloneOption枚举类型的参数,体现了被依赖的事务再上尚未接受到依赖事务的通知(调用Complete或者Rollback方法)得情况下,提交或者完成所采取的事务控制行为。DependentCloneOption提供了两个选项,BlockCommitUntilComplete表示被依赖事务会一直等待接收到依赖事务的通知或者超过事务设定的超时时限;而RollbackIfNotComplete则会直接将被依赖的事务回滚,并抛出TransactionAbortedException异常。

   1: [Serializable]
   2: public class Transaction : IDisposable, ISerializable
   3: {       
   4:     //其他成员
   5:     public DependentTransaction DependentClone(DependentCloneOption cloneOption);
   6: }
   7: public enum DependentCloneOption
   8: {
   9:     BlockCommitUntilComplete,
  10:     RollbackIfNotComplete
  11: }

下面的代码演示了如果通过依赖事务,采用异步的方式进行银行转账操作。借助于组件ThreadPool,将主线程环境事务的依赖事务传递给异步操作代理,开始异步操作的时候将此依赖事务作为当前的环境事务,那么之后的操作将自动在当前事务下进行。

   1: private static void Transfer(string accountFrom, string accountTo, double amount)
   2: {
   3:     Transaction originalTransaction = Transaction.Current;
   4:     CommittableTransaction transaction = new CommittableTransaction();
   5:     try
   6:     {
   7:         Transaction.Current = transaction;
   8:         ThreadPool.QueueUserWorkItem(state =>
   9:         {
  10:             Transaction.Current = state as DependentTransaction;
  11:             try
  12:             {
  13:                 Withdraw(accountFrom, amount);
  14:                 Deposite(accountTo, amount);
  15:                 (state as DependentTransaction).Complete();
  16:             }
  17:             catch (Exception ex)
  18:             {
  19:                 Transaction.Current.Rollback(ex);
  20:             }
  21:             finally
  22:             {
  23:                 (state as IDisposable).Dispose();
  24:                 Transaction.Current = null;
  25:             }
  26:         }, Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete));
  27:         //其他操作
  28:         transaction.Commit();
  29:     }
  30:     catch (TransactionAbortedException ex)
  31:     {
  32:         transaction.Rollback(ex);
  33:         Console.WriteLine("转帐失败,错误信息:{0}", ex.InnerException.Message);
  34:     }
  35:     catch (Exception ex)
  36:     {
  37:         transaction.Rollback(ex);
  38:         throw;
  39:     }
  40:     finally
  41:     {
  42:         Transaction.Current = originalTransaction;
  43:         transaction.Dispose();
  44:     }
  45: }

由于在调用DependentClone方法创建依赖事务时指定的参数为 DependentCloneOption.BlockCommitUntilComplete,所以主线程在调用Commit方法提交事务的时候,由于 依赖事务尚未结束(调用Complete或者Rollback方法),在这里会一直等待。如果依赖事务的Complete或者Rollback一直没有调 用,那么被依赖的事务会一直等到超出事务设置的超时时限。所以,对于基于BlockCommitUntilComplete选项创建的依赖事务来说,应该 及时地调用Complete或者Rollback方法。

2、通过DependentTransaction实现事务型方法

这里所说的事务型方法是指方法的执行总是在事务中执行。具体来讲,有两种不同的事务应用场景:如果当前不存在环境事务,那么方法的执行将在一个独立的事务中执行;反之,如果存在环境事务,在方法执行会自动加入到环境事务之中。

比如说,存储(Deposit)和提取(Withdraw)就是典型的事务型操作。对于单纯的存取款的场景,应该创建一个新的事务来控制存储和提取 操作的执行,以确保单一帐户款项的数据一致性。如果在转账的场景中,应在在转账开始之前就创建一个新的事务,让提取和存储的操作自动加入到这个事务之中。

我们现在就结合可提交事务和依赖事务将Deposit和Withdraw两个方法定义成事务型方法,为了相同代码的重复,在这里把事务控制部分定义在如下一个InvokeInTransaction静态方法中:

   1: static void InvokeInTransaction(Action action)
   2: {
   3:     Transaction originalTransaction = Transaction.Current;
   4:     CommittableTransaction committableTransaction = null;
   5:     DependentTransaction dependentTransaction = null;
   6:     if (null == Transaction.Current)
   7:     {
   8:         committableTransaction = new CommittableTransaction();
   9:         Transaction.Current = committableTransaction;
  10:     }
  11:     else
  12:     {
  13:         dependentTransaction = Transaction.Current.DependentClone(DependentCloneOption.RollbackIfNotComplete);
  14:         Transaction.Current = dependentTransaction;
  15:     } 
  16:  
  17:     try
  18:     {
  19:         action();
  20:         if (null != committableTransaction)
  21:         {
  22:             committableTransaction.Commit();
  23:         } 
  24:  
  25:         if (null != dependentTransaction)
  26:         {
  27:             dependentTransaction.Complete();
  28:         }
  29:     }
  30:     catch (Exception ex)
  31:     {
  32:         Transaction.Current.Rollback(ex);
  33:         throw;
  34:     }
  35:     finally
  36:     {
  37:         Transaction transaction = Transaction.Current;
  38:         Transaction.Current = originalTransaction;
  39:         transaction.Dispose();
  40:     }
  41: }

InvokeInTransaction方法的参数是一个Action类型的代理(Delegate),表示具体的业务操作。在开始的时候记录下当 前的环境事务,当整个操作结束之后应该环境事务恢复成该值。如果存在环境事务,则创建环境事务的依赖事务,反之直接创建可提交事务。并将新创建的依赖事务 或者可提交事务作为当前的环境事务。将目标操作的执行(action)放在try/catch中,当目标操作顺利执行后,调用依赖事务的Complete 方法或者可提交事务的Commit方法。如果抛出异常,则调用环境事务的Rollback进行回滚。在finally块中将环境事务恢复到之前的状态,并 调用Dispose方法对创建的事务进行回收。

借助于InvokeInTransaction这个辅助方法,我们以事务型方法的形式定义了如下的两个方法:Withdraw和Deposit,分别实现提取和存储的操作。

   1: static void Withdraw(string accountId, double amount)
   2: {           
   3:     Dictionary<string, object> parameters = new Dictionary<string, object>();
   4:     parameters.Add("id", accountId);
   5:     parameters.Add("amount", amount);
   6:     InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_WITHDRAW", parameters));
   7: }       
   8:  
   9: static void Deposit(string accountId, double amount)
  10: {
  11:     Dictionary<string, object> parameters = new Dictionary<string, object>();
  12:     parameters.Add("id", accountId);
  13:     parameters.Add("amount", amount);
  14:     InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_DEPOSIT", parameters));
  15: }

二、TransactionScope

在上面一节,我结合可提交事务和依赖事务,以及环境事务的机制提供了对事务型操作的实现。实际上,如果借助TransactionScope,相应的代码将会变得非常简单。下面的代码中,通过TransactionScope对InvokeInTransaction进行了改写,从执行效果来看这和原来的代码完全一致。

   1: static void InvokeInTransaction(Action action)
   2: {
   3:     using (TransactionScope transactionScope = new TransactionScope())
   4:     {
   5:         action();
   6:         transactionScope.Complete();
   7:     }
   8: }

通过InvokeInTransaction方法前后代码的对比,我们可以明显看到TransactionScope确实能够使我们的事务控制变得非常的简单。实际上,在利用System.Transactions事务进行编程的时候,我们一般不会使用到可提交事务,对于依赖事务也只有在异步调用的时候会使用到,基于TransactionScope的事务编程方式才是我们推荐的。

正如其名称所表现的一样,TransactionScope就是为一组事务型操作创建一个执行范围,而这个范围始于TransactionScope创建之时,结束于TransactionScope被回收(调用Dispose方法)。在对TransactionScope进行深入介绍之前,照例先来看看它的定义:

   1: public sealed class TransactionScope : IDisposable
   2: {
   3:     public TransactionScope();
   4:     public TransactionScope(Transaction transactionToUse);
   5:     public TransactionScope(TransactionScopeOption scopeOption);
   6:     public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout);
   7:     public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout);
   8:     public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions);
   9:     public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout, EnterpriseServicesInteropOption interopOption);
  10:     public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions, EnterpriseServicesInteropOption interopOption);
  11:     
  12:     public void Complete();
  13:     public void Dispose();    
  14: }

我们可以看到TransactionScope实现了IDisposable接口,除了Dispose方法之外,仅仅具有一个唯一的方法:Complete。但是TransactionScope却有一组丰富的构造函数。我们先来看看这些构造函数相应的参数如何影响TransactionScope对事务控制的行为。

1、TransactionScopeOption

实际上前面一节中提供的InvokeInTransaction方法基本上体现了TransactionScope的内部实现。也就是说,TransactionScope也是通过创建可提交事务或者依赖事务,并将其作为事务范围内的环境事务,从而将范围的所有操作纳入到一个事务之中。

通过在构造函数中指定TransactionScopeOption类型的scopeOption参数,控制TransactionScope当环境事务存在的时候应该采取怎样的方式执行事务范围内的操作。具有来讲,具有三种不同的方式:

  • 如果已经存在环境事务,则使用该环境事务。否则,在进入范围之前创建新的事务;
  • 总是为该范围创建新事务;
  • 环境事务上下文在创建范围时被取消。范围中的所有操作都在无环境事务上下文的情况下完成。

TransactionScopeOption是一个枚举,三个枚举值Required、RequiresNew和Suppress依次对应上面的三种行为。

   1: public enum TransactionScopeOption
   2: {
   3:     Required,
   4:     RequiresNew,
   5:     Suppress
   6: }

对于Required选项,如果当前存在环境事务TransactionScope会 创建环境事务的依赖事务,负责创建可提交事务,然后将创建的环境事务或者可提交事务作为事务范围的环境事务。如对于RequiresNew选 项,TransactionScope总是会创建可提交事务并将其作为事务范围的环境事务,意味着控制事务范围内操作的事务也当前的环境事务已经没有任何 关系。如果Suppress选项,TransactionScope会将事务范围内的环境事务设为空,意味着事务范围内的操作并不受事务的控制。

Required是默认选项,意味着事务范围内的事务将会作为当前环境事务的一部分。如果你不希望某个操作被纳入当前的环境事务,但是相应的操作也 需要事务的控制以确保所操作数据的一致性。比如,当业务逻辑失败导致异常抛出,需要对相应的错误信息进行日志记录。对于日记的操作就可以放入基于RequiresNew选项创建TransactionScope中。对于一些不重要的操作(操作的错误可被忽略),并且不需要通过事务来控制的操作,比如发送一些不太重要的通知,就可以采用Suppress选项。

2、TransactionOptions和EnterpriseServicesInteropOption

TransactionOptions在前面已经提及,用于控制事务的超时时限和隔离级别。对于超时时限,你也可以选择 TransactionScope相应能够的构造函数以TimeSpan的形式指定。而对于事务的隔离级别,需要着重强调一点:当选择 TransactionScopeOption.Required选项时,TransactionScope指定的隔离级别必须与环境事务(如果有)相匹 配。

比如下面的例子中,我定义两个嵌套的TransactionScope,外部的TransactionScope采用默认的隔离级别,内部在采用ReadCommitted隔离级别,当执行这段代码的时候,会抛出如图1所示的ArgumentException异常。

   1: using (TransactionScope outerScope = new TransactionScope())
   2: {
   3:     TransactionOptions transactionOptions = new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted };
   4:     using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
   5:     {
   6:         //事务型操作
   7:         innerScope.Complete();
   8:     }
   9:     //事务型操作
  10:     outerScope.Complete();
  11: }

image

图1 隔离级别不一致导致的异常

实际上在System.Transactions事务机制被引入之前,像Enterprise Service主要依赖于基于COM+的分布式事务。TransactionScope通过 EnterpriseServicesInteropOption控制System.Transactions事务如何与COM+的分布式事务进行互操 作。具有来讲具有如下三种互操作选项,分别和EnterpriseServicesInteropOption三个枚举值相对应:

  • None:Transaction 和 Current 之间不同步;
  • Automatic:搜索现有的 COM+ 上下文并与之同步(如该上下文存在);
  • Full:System.EnterpriseServices 上下文(可通过调用 ContextUtil 类的静态方法 Transaction 来检索)和 System.Transactions 环境事务(可通过调用 Transaction 类的静态方法 Current 来检索)始终保持同步。这将引入性能损失,因为可能需要创建新的 System.EnterpriseServices 上下文。
   1: public enum EnterpriseServicesInteropOption
   2: {
   3:     None,
   4:     Automatic,
   5:     Full
   6: }

3、事务提交和回滚

对于事务范围中的事务,无论是事务的提交(对于可提交事务)、完成(依赖事务)和回滚都是在Dispose方法中执行的。 TransactionScope中定一个个私有的布尔类型字段(complete)表示事务是否正常结束。该成员的默认值为False,当调用 TransactionScope的Complete方法的时候会将此字段设置成True。当Dispose执行的时候,如果该字段的值为False,会 调用事务的Rollback方法对该事务实施回滚;否则会调用Commit方法(对于可提交事务)对事务进行提交或者调用Complete方法(依赖事 务)通知被依赖的事务本地事务已经正常完成。

除了执行事务的提交、完成或者回滚之外,TransactionScope的Dispose方法还负责将环境事务回复到事务范围开始之前的状态。在 调用Complete和Dispose之前,环境事务处于不可用的状态,如果此时试图获取环境事务,会抛出异常。比如在下面的代码中,在事务范围内部调用 Complete方法后,通过Transaction的Current静态属性获取当前环境事务,会抛出图2所示的InvalidOpertionException异常。

   1: using (TransactionScope transactionScope = new TransactionScope())
   2: {
   3:     //其他事务操作
   4:     transactionScope.Complete();
   5:     Transaction ambientTransaction = Transaction.Current;
   6: }  

image 图2 在TransactionScope完成之后获取环境事务导致的异常

转载于:https://www.cnblogs.com/duanxz/p/4282006.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/257644.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS开发系列--触摸事件、手势识别、摇晃事件、耳机线控

-- iOS事件全面解析 概览 iPhone的成功很大一部分得益于它多点触摸的强大功能&#xff0c;乔布斯让人们认识到手机其实是可以不用按键和手写笔直接操作的&#xff0c;这不愧为一项伟大的设计。今天我们就针对iOS的触摸事件&#xff08;手势操作&#xff09;、运动事件、远程控制…

python为什么忽然火了_为什么Python突然就火了起来了呢?

近日&#xff0c;TIOBE发布10月编程语言排行榜显示&#xff0c;15年来TIOBE指数的前8名一直保持不变&#xff0c;而Python正在成为一种新的大型语言。越来越多的企业在使用Python进行开发&#xff0c;越来越多的人正在加入Python程序员行列!TIOBE 10月编程语言排行榜前20名Pyth…

ARP扫描工具arp-scan

2019独角兽企业重金招聘Python工程师标准>>> ARP扫描工具arp-scan arp-scan是Kali Linux自带的一款ARP扫描工具。该工具可以进行单一目标扫描&#xff0c;也可以进行批量扫描。批量扫描的时候&#xff0c;用户可以通过CIDR、地址范围或者列表文件的方式指定。该工具…

elementui el-from 怎样显示图片_vue2.0使用weui.js的uploader组件上传图片(兼容移动端)...

本文已同步到专业技术网站 www.sufaith.com, 该网站专注于前后端开发技术与经验分享, 包含Web开发、Nodejs、Python、Linux、IT资讯等板块.最近在使用 vue2.0开发微信公众号网页 其中涉及到 选择图片, 图片的压缩上传, 预览, 删除等操作。项目整体UI框架使用的是 vux, 但可惜的…

面向对象分析

在需求获取阶段&#xff0c;开发人员关注于理解用户以及他们的使用要求。而在需求分析阶段&#xff0c;开发人员关注于理解系统需要构建的内容&#xff0c;其核心是产生一个准确的、完整的、一致的和可验证的系统模型&#xff0c;称为分析模型。 面对对象的分析模型由三个独立的…

51nod 1050 循环数组最大子段和

1050 循环数组最大子段和 N个整数组成的循环序列a[1],a[2],a[3],…,a[n]&#xff0c;求该序列如a[i]a[i1]…a[j]的连续的子段和的最大值&#xff08;循环序列是指n个数围成一个圈&#xff0c;因此需要考虑a[n-1],a[n],a[1],a[2]这样的序列&#xff09;。当所给的整数均为负数时…

Spark- Linux下安装Spark

Spark- Linux下安装Spark 前期部署 1.JDK安装&#xff0c;配置PATH 可以参考之前配置hadoop等配置 2.下载spark-1.6.1-bin-hadoop2.6.tgz,并上传到服务器解压 [rootsrv01 ~]# tar -xvzf spark-1.6.1-hadoop2.6.tgz /usr/spark-1.6.1-hadoop2.6 3.在 /usr 下创建软链接到目标文…

python需要背的英语单词怎么写_学Python必须背的42个常见单词,看看你都会吗?...

这42个单词是学习Python必须背会的单词&#xff0c;也是代码中常见的单词。希望你能都背下来&#xff01;&#xff01;1. adult [ˈdʌlt] 成年人2. authentication [ɔːˌθentɪˈkeɪʃn] 身份验证、认证、鉴定3. bit [bɪt] 稍微、小量、小块、一点4. byte [baɪt] …

asp.net mvc4开启SqlServer 会话共享模式

2019独角兽企业重金招聘Python工程师标准>>> 应用部署结构&#xff08;精简&#xff09;: 站点部署在Nginx后面&#xff0c;以Nginx作为反向代理&#xff0c;不希望在Nginx上设置ip_hash&#xff0c;实现比较真实的负载均衡效果。 这时考虑到需要让site1和site2同时…

【转】(五)unity4.6Ugui中文教程文档-------概要-UGUI Interaction Components

原创至上&#xff0c;移步请戳&#xff1a;&#xff08;五&#xff09;unity4.6Ugui中文教程文档-------概要-UGUI Interaction Components 4、Interaction Components 本节涵盖了处理交互&#xff0c;例如鼠标或触摸事件和使用键盘或控制器交互的 UI系统中的组件。 4.1 Select…

j2ee 简单网站搭建:(十)jquery ztree 插件使用入门

为什么80%的码农都做不了架构师&#xff1f;>>> 《j2ee 简单网站搭建&#xff1a;&#xff08;一&#xff09; windows 操作系统下使用 eclipse 建立 maven web 项目》《j2ee 简单网站搭建&#xff1a;&#xff08;二&#xff09;添加和配置 spring spring-mvc 的…

实习报告

实习时间&#xff1a;2016/2/18-2016/2/24 实习地点&#xff1a;陕西省米脂县公安局网络警察大队     实习报告&#xff1a; 如今的社会&#xff0c;网络高度发展&#xff0c;一些人随着时代的潮流利用网络发家致富&#xff0c;而有些人利用网络监管的一些漏洞&#xff0c;…

Android成长日记-使用GridView显示多行数据

本节将实现以下效果 Ps&#xff1a;看起来很不错的样子吧&#xff0c;而且很像九宫格/se ----------------------------------------------------------------------- 下面进入正题[s1] &#xff1a; Step 1&#xff1a;新建Layout&#xff0c;里面创建GridView <GridView a…

夺命雷公狗---微信开发39----微信语言识别接口1

语音识别接口的基本介绍 注意&#xff1a; 由于客户端缓存&#xff0c;开发者开启或者关闭语音识别功能&#xff0c;对新关注者立即生效&#xff0c;对已关注用户需要24小时生效&#xff0c;开发者可以从新关注帐号进行测试。 我们可以在测试号下方的体验接口权限表里面找到“接…

java applet 文本框_Java Applet 文本框 TextField 小例 | 学步园

一个Java Applet程序中必须有一个类是Applet类的子类&#xff0c;成为该子类是Java Applet的主类&#xff0c; 并且必须是public class。 Applet类是包java.applet中的一个类&#xff0c; 同时它还是包java.awt中Container(容器)类的子类。因此Java Applet的主类的实例是一个容…

博客园客户端(睡睡版iphone)源码

1.关于 https://itunes.apple.com/us/app/shui-shui-bo-ke-yuan/id512394144?ls1&mt8 项目目前为V3.0版&#xff0c;也是我开发的最新版&#xff0c;目前已无法在appstore下载&#xff0c;项目介绍&#xff1a;http://www.cnblogs.com/bandy/p/3509482.html 2.现状 目前本…

3.过滤数据 ---SQL

一、使用WHERE子句 SELECT prod_name, prod_price FROM Products WHERE prod_price 3.49; 输出▼ prod_name prod_price ------------------- ---------- Fish bean bag toy 3.49 Bird bean bag toy 3.49 Rabbit bean bag toy 3.49 分析▼ 这条语句从products表中检索两个列&a…

IOS-C语言第8天,Struct (结构体)

转载于:https://www.cnblogs.com/xiangrongsu/p/4309160.html

java concurrent 锁_java并发机制锁的类型和实现

synchronized 和 volatile&#xff0c;是最基础的两个锁&#xff01;volatile是轻量级锁&#xff0c;它在多核处理器开发中保证了共享变量的可见性。即当一个线程修改一个共享变量时&#xff0c;其他线程能够读到这个修改的值。它比syncronized使用和成本更低。要说volatile的实…

【起航计划 011】2015 起航计划 Android APIDemo的魔鬼步伐 10 App-Activity-Reorder Activities 后退栈 Intent FLAG...

Reorder Activities 示例有四个相关的Activitives: ReorderOnLaunch, ReorderTwo,ReorderThree, ReorderFour。其中ReorderOnLaunch为主Activity&#xff0c;ReorderOnLaunch启动ReorderTwo &#xff0c;ReorderTwo启动 ReorderThree&#xff0c;ReorderThree启动 ReorderFour。…