zoukankan      html  css  js  c++  java
  • 【WCF--初入江湖】07 分布式事务

    07 分布式事务

    一、前言

      【1】理解事务特性
      【2】掌握TransactionFlow 特性
      【3】掌握WCF中的事务属性
          TransactionAutoCompleteOnSessionClose
          TransactionlsolationLevel
          TransactionTimeout
    二、事务
        【1】事务概述
        事务是一个最小的工作单元,不论成功与否都作为一个整体进行工作。   
      由于事务是由几个任务组成的,因此如果一个事务作为一个整体是成功的,则事务中的每个任务都必须成功。
      如果事务中有一部分失败,则整体事务失败。 当事务失败时,系统返回到事务开始前的状态。这个取消所有变化的过程称为“回滚”( rollback )。
      例如,如果一个事务成功更新了两个表,在更新第三个表时失败,则系统将两次更新恢复原状,并返回到原始的状态。 
     
        【2】事务的特性
          一个纯粹的事务包含4个特性
            Atomic(原子性)
            Consistent(一致性)
            Isolated(隔离性)
            Durable(持久性)
     
     
    三、TransactionFlow 特性
      【1】概述
        [TransactionFlow]是提定服务操作可以接收事务的模式,
      该Attribute只有一个特性用来标记服务操作的方法。即:TransactionFlowOption
      
      TransactionFlowOption是一个枚举型,包括三个枚举项
    –      NotAllowed:不允许事务,是缺省值;
    –      Allowed:允许事务,意味着事务可有可无;
    –      Mandatory:强制事务,表示事务是必须的。
     
      【2】使用;在服务方法上添加该特性
    [TransactionFlow(TransactionFlowOption.Mandatory)]
    int serviceMethod(int value)
    {
      
    }

         

    四、[ServiceBehavior]中的事务属性

      【1】TransactionAutoCompleteOnSessionClose

          如果想要确保关闭会话后待处理的消息仍然可以完成,应该使用该属性。

      根据其属性值,事务将会在会话关闭后提交或回滚。

     
      【2】TransactionIsolationLevel

        TransactionIsolationLevel用于指示事务隔离级别。

          IsolationLevel枚举如下:

        (1)ReadUncommitted:读取未提交数据,该方式在读取数据时保持共享锁定以避免读取已修改的数据,但在事务结束前可以更改这些数据,这导致非可重复读取或幻读。

        (2)ReadCommitted:读取提交数据, 发出共享锁定并允许非独占方式的锁定。该方式与读取未提交数据相相似,这种方式看似和读取未提交数据相似,但有一个区别,事务的只读锁在移到下一行的时候,会解锁,而写入锁却只有在事务完成或者被中止后才解锁,事务要等待所有写入锁解锁。

        (3)RepeatableRead:可重复性读取,与读取提交数据相似,在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。防止非可重复读取,但幻读行仍有可能发生。该方式是只读锁也要等到事务结束或者中止才解除

        (4)Serializable:在完成事务前防止更新或插入。

        ReadUncommitted是最低的隔离级别

        Serializable是最高的隔离级别

                ---------------

                理解事务隔离级别:

                  (1)脏读:一个事务会读进还没有被另一个事务提交的数据,所以你会看到一些最后被另一个事务回滚掉的数据。(读了别人正在更改的数据)
           (2)非可重复性读取:一个事务读进一条记录,另一个事务更改了这条记录并提交完毕,这时候第一个事务再次读这条记录时,它已经改变了。(更改了别人正在读的数据
           (3)幻像读:一个事务用Where子句来检索一个表的数据,另一个事务插入一条新的记录,并且符合Where条件,这样,第一个事务用同一个where条件来检索数据后,就会         多出一条记录。(读了别人正在更改但未提交的数据
     
       【3】TransactionTimeout

          用于指示事务的超时时间,默认为TimeSpan.Zero,表示不会受超时时间的限制。

    示例:

    ServiceBehavior(TransactionAutoCompleteOnSessionClose=true,
             TransactionIsolationLevel=IsolationLevel.ReadCommitted,
             TransactionTimeout="00:00:30")] public Class ServiceClass:IServiceClass { }

    五、实例

      Client/Service transaction,
      最常见的一种事务模型,通常由客户端或服务本身启用一个事务。

        设置步骤:

          (1) 服务的EndPoint和客户端的EndPoint都设置一个支持事务的Binding,设置 Binding的属性TransactionFlow = true。
          (2) 在接口的方法上设置 TransactionFlow(TransactionFlowOption.Allowed)。
          (3) 在实现类的方法上设置 OperationBehavior(TransactionScopeRequired=true)。
     
         支持事务的Binding有:
        wsHttpBinding
        wsDualHttpBinding
        netTcpBinding
        netNamePipeBinding
     
      案例演示:
     
    第一步:创建一个数据库
     
    Create database WCFTransactionDb;
    use WCFTransactionDb;
    Create table Account
    (
        Id int not null primary key, --账号
        Balance float                --余额
    )
    insert into Account values(1000, 3000.0);
    insert into Account values(1001, 2000.0);

     更新和查看:

    update Account set Balance=Balance-200
    where Id=1000;
    
    update Account set Balance=Balance+200
    where Id=1001;
    
    SELECT TOP 1000 [Id]
          ,[Balance]
      FROM [WCFTransactionDb].[dbo].[Account]

     

     
    第二步:添加接口添及其实现类
    【ServiceBehavior】的有关属性 及其要实现事务的方法添加[OperationBehavior(TransactionScopeRequired = true ]
    IService1.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    
    namespace Keasy5.WCF.Transaction.WCFService
    {
        [ServiceContract]
        public interface IService1
        {
            [OperationContract]
            [TransactionFlow(TransactionFlowOption.Mandatory/*强制事务必须成为流*/)] 
            void OutMoney(int id, double money);
    
            [OperationContract]
            [TransactionFlow(TransactionFlowOption.Mandatory/*强制事务必须成为流*/)]
            void IntoMoney(int id, double money);
        }
    }

    Service1.cs
    namespace Keasy5.WCF.Transaction.WCFService
    {
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single/*对象用于所有传入呼叫,并且在调用后不回收。如果服务对象不存在,则创建一个。*/
            ,ReleaseServiceInstanceOnTransactionComplete = false /*获取或设置一个值,该值指定在完成当前事务后是否释放服务对象*/
            )]
        public class Service1 : IService1
        {
            [OperationBehavior(TransactionScopeRequired = true /*该值指示方法在执行时是否需要事务范围*/)]
            public void OutMoney(double money)
            {
                
            }
    
            [OperationBehavior(TransactionScopeRequired = true /*该值指示方法在执行时是否需要事务范围*/)]
            public void IntoMoney(double money)
            {
                
            }
        }
    }
     第三步:添加访问数据库数据逻辑:
     
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    
    namespace Keasy5.WCF.Transaction.WCFService
    {
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single/*对象用于所有传入呼叫,并且在调用后不回收。如果服务对象不存在,则创建一个。*/
            ,ReleaseServiceInstanceOnTransactionComplete = false /*获取或设置一个值,该值指定在完成当前事务后是否释放服务对象*/
            )]
        public class Service1 : IService1
        {
            private const string ConnectString = "Data Source=.;Initial Catalog=WCFTransactionDb;Integrated Security=True";
    
            [OperationBehavior(TransactionScopeRequired = true /*该值指示方法在执行时是否需要事务范围*/)]
            public void OutMoney(int id, double money)
            {
                ChangeMoneyToDb(id, money, false);
            }
    
    
    
            [OperationBehavior(TransactionScopeRequired = true /*该值指示方法在执行时是否需要事务范围*/)]
            public void IntoMoney(int id, double money)
            {
                ChangeMoneyToDb(id, money, true);
            }
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="id"></param>
            /// <param name="money"></param>
            /// <param name="inMoney">inMoney=true表示转入,否则表示转出</param>
            private static void ChangeMoneyToDb(int id, double money, bool inMoney)
            {
                using (SqlConnection sqlConnection = new SqlConnection(ConnectString))
                {
                    sqlConnection.Open();
    
                    using (SqlCommand command = sqlConnection.CreateCommand())
                    {
                        string updateSql = null;
                        if (inMoney)
                        {
                             updateSql = "update Account set Balance=Balance+@Money where Id=@Id;";
                            //updateSql = string.Format("update Account set Balance=Balance+{0} where Id={1}", money, id)+";";
    
                        }
                        else
                        {
                            updateSql = "update Account set Balance=Balance-@Money where Id=@Id;";
                            //updateSql = string.Format("update Account set Balance=Balance-{0} where Id={1}", money, id)+";";
    
                        }
    
                        command.CommandText = updateSql;
                        command.CommandType = CommandType.Text;
                        command.Parameters.Add(new SqlParameter("@Id", id));
                        command.Parameters.Add(new SqlParameter("@Money", money));
    
                        command.ExecuteNonQuery();
                    }
                }
            }
        }
    }
    View Code
    第四步:服务端配置服务:
     
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.web>
        <compilation debug="true" />
      </system.web>
      <system.serviceModel>
        <bindings>
          <wsHttpBinding>
            <binding  name="wsHttpBinding_translationBind" transactionFlow="true"></binding>
          </wsHttpBinding>
        </bindings>
        <services>
          <service name="Keasy5.WCF.Transaction.WCFService.Service1">
            <host>
              <baseAddresses>
                <add baseAddress = "http://localhost:8733/Design_Time_Addresses/Keasy5.WCF.Transaction.WCFService/Service1/" />
              </baseAddresses>
            </host>
            <endpoint address="bank" 
                      binding="wsHttpBinding"
                      bindingConfiguration="wsHttpBinding_translationBind"
                      contract="Keasy5.WCF.Transaction.WCFService.IService1"></endpoint>
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <serviceMetadata httpGetEnabled="True"/>
              <serviceDebug includeExceptionDetailInFaults="False" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>
    
    </configuration>
    注意:常见异常
    System.InvalidOperationException: “Service1”协定上至少有一个操作配置为将 TransactionFlowAttribute 特性设置为“强制”,但是通道的绑定“BasicHttpBinding”未使用 TransactionFlowBindingElement 进行配置。没有 TransactionFlowBindingElement,无法使用设置为“强制”的 TransactionFlowAttribute 特性。
       在 System.ServiceModel.Dispatcher.TransactionValidationBehavior.ValidateTransactionFlowRequired(String resource, String name, ServiceEndpoint endpoint)
       在 System.ServiceModel.Dispatcher.TransactionValidationBehavior.System.ServiceModel.Description.IServiceBehavior.Validate(ServiceDescription service, ServiceHostBase serviceHostBase)
       在 System.ServiceModel.Description.DispatcherBuilder.ValidateDescription(ServiceDescription description, ServiceHostBase serviceHost)
       在 System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost)
       在 System.ServiceModel.ServiceHostBase.InitializeRuntime()
       在 System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
       在 System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
       在 Microsoft.Tools.SvcHost.ServiceHostHelper.OpenService(ServiceInfo info)
    View Code

    解决方法是:为endpoint 配置一个transactionFlow="true"的Binding。

        <bindings>
          <wsHttpBinding>
            <binding  name="wsHttpBinding_translationBind" transactionFlow="true"></binding>
          </wsHttpBinding>
        </bindings>
            <endpoint address="bank" 
                    。。。
                      bindingConfiguration="wsHttpBinding_translationBind"
    。。。。
    ></endpoint>
     第五步:客户端调用;
        【1】先配置配置文件: 
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <system.serviceModel>
            <bindings>
                <wsHttpBinding>
                    <binding name="WSHttpBinding_IService1" 
                             transactionFlow="true" />
                </wsHttpBinding>
            </bindings>
            <client>
                <endpoint address="http://localhost:8733/Design_Time_Addresses/Keasy5.WCF.Transaction.WCFService/Service1/bank"
                          binding="wsHttpBinding" 
                          bindingConfiguration="WSHttpBinding_IService1"
                          contract="TransactionWCFService.IService1" 
                          name="Transaction_IService1">
                </endpoint>
            </client>
        </system.serviceModel>
    </configuration>
       【2】调用:
            private void buttonChangedAccount_Click(object sender, EventArgs e)
            {
                int outId = Convert.ToInt32(this.textBoxOutId.Text);
                int inId = Convert.ToInt32(this.textBoxInId.Text);
                double money = Convert.ToDouble(this.textBoxMoney.Text);
    
                Service1Client client = new Service1Client("Transaction_IService1");
    
                using (System.Transactions.TransactionScope transactionScope = new TransactionScope())
                {
                    client.OutMoney(outId, money);
                    client.IntoMoney(inId, money);
    
                    transactionScope.Complete();
                }
            }

     

      【3】测试事务

      到目前,转账功能完成,

         为测试事务是否成功,服务端或客户端故意中抛出一个异常。

            private void buttonChangedAccount_Click(object sender, EventArgs e)
            {
                int outId = Convert.ToInt32(this.textBoxOutId.Text);
                int inId = Convert.ToInt32(this.textBoxInId.Text);
                double money = Convert.ToDouble(this.textBoxMoney.Text);
    
                Service1Client client = new Service1Client("Transaction_IService1");
    
                using (System.Transactions.TransactionScope transactionScope = new TransactionScope())
                {
                    try
                    {
                        client.OutMoney(outId, money);
                        client.IntoMoney(inId, money);
    
                        throw new FaultException("发生故障,转账失败");
    
                        transactionScope.Complete();
                    }
                    catch (FaultException faultException)
                    {
                        System.Transactions.Transaction.Current.Rollback();
                        MessageBox.Show(faultException.Message);
                    }
    
                }
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single/*对象用于所有传入呼叫,并且在调用后不回收。如果服务对象不存在,则创建一个。*/
            ,ReleaseServiceInstanceOnTransactionComplete = false /*获取或设置一个值,该值指定在完成当前事务后是否释放服务对象*/
            )]
        public class Service1 : IService1
        {
    。。。。。
            [OperationBehavior(TransactionScopeRequired = true /*该值指示方法在执行时是否需要事务范围*/)]
            public void IntoMoney(int id, double money)
            {
                ChangeMoneyToDb(id, money, true);
    
                throw new FaultException("发生故障,转账失败");
            }
    
    。。。。。。。。。

     出现异常后,两个账号的余额均没有改变。事务起效了。

      如果调用不支持事务的服务方法,会是什么样子的

    为了进行探究:在IService和Service1,添加两个不支持事务的两个方法:

    OutMoneyNoTransaction和IntoMoneyNoTransaction

    IService1.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    
    namespace Keasy5.WCF.Transaction.WCFService
    {
        [ServiceContract]
        public interface IService1
        {
            [OperationContract]
            [TransactionFlow(TransactionFlowOption.Mandatory/*强制事务必须成为流*/)]
            void OutMoney(int id, double money);
    
            [OperationContract]
            [TransactionFlow(TransactionFlowOption.Mandatory/*强制事务必须成为流*/)]
            void IntoMoney(int id, double money);
    
            [OperationContract]
            void OutMoneyNoTransaction(int id, double money);
    
            [OperationContract]
            void IntoMoneyNoTransaction(int id, double money);
        }
    }

    Service1.cs

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    
    namespace Keasy5.WCF.Transaction.WCFService
    {
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single/*对象用于所有传入呼叫,并且在调用后不回收。如果服务对象不存在,则创建一个。*/
            ,ReleaseServiceInstanceOnTransactionComplete = false /*获取或设置一个值,该值指定在完成当前事务后是否释放服务对象*/
            )]
        public class Service1 : IService1
        {
            private const string ConnectString = "Data Source=.;Initial Catalog=WCFTransactionDb;Integrated Security=True";
    
          。。。。
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="id"></param>
            /// <param name="money"></param>
            /// <param name="inMoney">inMoney=true表示转入,否则表示转出</param>
            private static void ChangeMoneyToDb(int id, double money, bool inMoney)
            {
                using (SqlConnection sqlConnection = new SqlConnection(ConnectString))
                {
                    sqlConnection.Open();
    
                    using (SqlCommand command = sqlConnection.CreateCommand())
                    {
                        string updateSql = null;
                        if (inMoney)
                        {
                             updateSql = "update Account set Balance=Balance+@Money where Id=@Id;";
                            //updateSql = string.Format("update Account set Balance=Balance+{0} where Id={1}", money, id)+";";
    
                        }
                        else
                        {
                            updateSql = "update Account set Balance=Balance-@Money where Id=@Id;";
                            //updateSql = string.Format("update Account set Balance=Balance-{0} where Id={1}", money, id)+";";
    
                        }
    
                        command.CommandText = updateSql;
                        command.CommandType = CommandType.Text;
                        command.Parameters.Add(new SqlParameter("@Id", id));
                        command.Parameters.Add(new SqlParameter("@Money", money));
    
                        command.ExecuteNonQuery();
                    }
                }
            }
    
    
            public void OutMoneyNoTransaction(int id, double money)
            {
                ChangeMoneyToDb(id, money, false);
            }
    
            public void IntoMoneyNoTransaction(int id, double money)
            {
                ChangeMoneyToDb(id, money, true);
    
                throw new FaultException("发生故障,转账失败");
            }
        }
    }

    客户端和服务端的配置不需要改变。

    客户端的调用,如下:

            private void ChangeAccountNoTrasacion_Click(object sender, EventArgs e)
            {
                int outId = Convert.ToInt32(this.textBoxOutId.Text);
                int inId = Convert.ToInt32(this.textBoxInId.Text);
                double money = Convert.ToDouble(this.textBoxMoney.Text);
    
                Service1Client client = new Service1Client("Transaction_IService1");
    
                using (System.Transactions.TransactionScope transactionScope = new TransactionScope())
                {
                    try
                    {
                        client.OutMoneyNoTransaction(outId, money);
                        client.IntoMoneyNoTransaction(inId, money);
    
                        transactionScope.Complete();
                    }
                    catch (FaultException faultException)
                    {
                        System.Transactions.Transaction.Current.Rollback();
                        MessageBox.Show(faultException.Message);
                    }
    
                }
            }

      虽然调用服务的两个不支持事务的方法被放在:

      transactionScope = new TransactionScope()中
                using (System.Transactions.TransactionScope transactionScope = new TransactionScope())
                {
                       。。。。
                       client.OutMoneyNoTransaction(outId, money);
                       client.IntoMoneyNoTransaction(inId, money);
                       。。。。

      但是两个账号的余额还是发生了改变。事务没有起效。

    其他事务

          【1】SQL中的事务处理

        我们可以通过如下三个SQL语句实现事务的启动、提交与回滚:

                BEGIN TRANSACTION: 开始一个事务;
                COMMIT TRANSACTION:提交事务
                ROLLBACK TRANSACTION:回滚事务
     
      【2】ADO.NET中事务处理
          将基于单个数据库连接的多个操作纳入同一个事务之中。
    using (DbTransaction transaction = connection.BeginTransaction())
    {
    
    }

     示例:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data;
    using System.Data.SqlClient;
    namespace WindowsFormsApplication1
    {
        class Class1
        {
            SqlConnection conn;  //连接对象
            SqlTransaction tran; //事务对象
            public Class1()
            {
                conn = new SqlConnection("server=.;uid=sa;pwd=sa;database=master");
            }
            //转出
            private void OutMoney(int m)
            {
                SqlCommand cmd = new SqlCommand("update account set balance=balance-" + m + " where ID='A'", conn);
                cmd.Transaction = tran;
                cmd.ExecuteNonQuery();
            }
    
            //转入
            private void intoMoney(int m)
            {
                SqlCommand cmd = new SqlCommand("update account set balance=balance+" + m + " where ID='B'", conn);
                cmd.Transaction = tran;
                cmd.ExecuteNonQuery();
            }
    
            //公开转帐
            public void TransferMoney(int m)
            {
                conn.Open();
                tran = conn.BeginTransaction(); //开启事务
                try
                {
                    OutMoney(m);
                    intoMoney(m);
                    tran.Commit(); //提交事务
                }
                catch (Exception err)
                {
                    tran.Rollback();  //回滚事务
                }
                finally
                {
                    conn.Close();
                }
            }
        }
    }
    View Code

     源码下载

      链接: http://pan.baidu.com/s/1pJPqg7h 密码: 6yjh

     
  • 相关阅读:
    HDU-1225 Football Score
    HDU-3854 LOOPS
    HDU-3863 No Gambling
    poj-2096 Collecting Bugs
    HDU-4336 Card Collector
    HDU-4405 Aeroplane chess
    2010上交:最小面积子矩阵
    VijosP1443:银河英雄传说
    VijosP1250:分组背包
    Vijos1221:神秘的配方
  • 原文地址:https://www.cnblogs.com/easy5weikai/p/3825362.html
Copyright © 2011-2022 走看看