zoukankan      html  css  js  c++  java
  • Spring.NET实用技巧4——NHibernate分布式事务(下)

      

      上篇,我们已实现了在同一应用程序下的分布式事务——即多Dao层+同Service层,每个Dao对应一个数据库,一个Service调用多个Dao。但是在一些特定的子系统较多的项目中,开发人员是无法访问到某个子系统的数据库,这就意味着不能通过增加Dao层来实现分布式事务。正如一个银行的软件系统,记录了客户的账户信息和存款金额,北京的分公司和上海的分公司分别有自己的数据库和软件系统。现在,要实现北京的系统向上海的系统转账,然而各自作为开发人员来说,没有足够的权限去访问对方的数据库,但是可以提供Web Service的方式去访问其系统服务。这样,我们就需要实现基于Web Service的分布式事务。

      实现基于Web Service的分布式事务的方法比较多,可以通过.NET企业服务的方式。但是为了更好的实现,我们选择WCF作为一个分布式应用程序框架。WCF在实现分布式事务中有它的优越之处。其思路在于启动MSDTC服务,将客户端的事务以流的方式传递到服务器端,在服务器端执行通过时,客户端再提交事务,相反则回滚事务。

      我们模仿上篇的场景做一个demo,并使用上篇的Dao和Domain。

      

      一、启动MSDTC服务。

      二、Service层

      ①.Customer

      

    CustomerManager
        public interface ICustomerManager
        {
            CustomerInfo Get(
    object id);

            
    object Save(CustomerInfo entity);

            
    void Update(CustomerInfo entity);
        }

        
    public class CustomerManager : ICustomerManager
        {
            
    private ICustomerDao Dao { getset; }

            
    public CustomerInfo Get(object id)
            {
                
    return Dao.Get(id);
            }

            
    public object Save(CustomerInfo entity)
            {
                
    return Dao.Save(entity);
            }

            
    public void Update(CustomerInfo entity)
            {
                
    if (entity.Money > 3000)
                {
                    
    throw new Exception("订金上限");
                }
                Dao.Update(entity);
            }
        }

      

    Service.xml
    <?xml version="1.0" encoding="utf-8" ?>
    <objects xmlns="http://www.springframework.net">

      
    <object id="transactionManager"
            type
    ="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21">
        
    <property name="DbProvider" ref="DbProvider"/>
        
    <property name="SessionFactory" ref="NHibernateSessionFactory"/>
      
    </object>

      
    <object id="transactionInterceptor" type="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data">
        
    <property name="TransactionManager" ref="transactionManager"/>
        
    <property name="TransactionAttributeSource">
          
    <object type="Spring.Transaction.Interceptor.AttributesTransactionAttributeSource, Spring.Data"/>
        
    </property>
      
    </object>

      
    <object id="BaseTransactionManager"  type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data" abstract="true">
        
    <property name="PlatformTransactionManager" ref="transactionManager"/>
        
    <property name="TransactionAttributes">
          
    <name-values>  
            
    <add key="*" value="PROPAGATION_REQUIRED"/>
          
    </name-values>
        
    </property>
      
    </object>

      
    <object id="Customer.CustomerManager" parent="BaseTransactionManager">
        
    <property name="Target">
          
    <object type="Customer.Service.Implement.CustomerManager, Customer.Service">
            
    <property name="Dao" ref="Customer.CustomerDao"/>
          
    </object>
        
    </property>
      
    </object>

    </objects>

      ②.Order

      

    OrderManager
        public interface IOrderManager
        {
            
    object Save(OrderInfo entity);
        }

        
    public class OrderManager : IOrderManager
        {
            
    public IOrderDao Dao { getset; }

            
    public object Save(OrderInfo entity)
            {
                
    return Dao.Save(entity);
            }
        }
    Service.xml
    <?xml version="1.0" encoding="utf-8" ?>
    <objects xmlns="http://www.springframework.net">

      
    <object id="transactionManager"
            type
    ="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21">
        
    <property name="DbProvider" ref="DbProvider"/>
        
    <property name="SessionFactory" ref="NHibernateSessionFactory"/>
      
    </object>

      
    <object id="transactionInterceptor" type="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data">
        
    <property name="TransactionManager" ref="transactionManager"/>
        
    <property name="TransactionAttributeSource">
          
    <object type="Spring.Transaction.Interceptor.AttributesTransactionAttributeSource, Spring.Data"/>
        
    </property>
      
    </object>

      
    <object id="BaseTransactionManager"  type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data" abstract="true">
        
    <property name="PlatformTransactionManager" ref="transactionManager"/>
        
    <property name="TransactionAttributes">
          
    <name-values>  
            
    <add key="*" value="PROPAGATION_REQUIRED"/>
          
    </name-values>
        
    </property>
      
    </object>

      
    <object id="Order.OrderManager" parent="BaseTransactionManager">
        
    <property name="Target">
          
    <object type="Order.Service.Implement.OrderManager, Order.Service">
            
    <property name="Dao" ref="Order.OrderDao"/>
          
    </object>
        
    </property>
      
    </object>

    </objects>

       三、服务契约和Host。

      1、契约

      作为服务契约,需要启用Session,并且设置TransactionFlowOption的等级为Allowed或Mandatory来接收客户端事务流。

      作为契约的实现部分,需要设置TransactionScopeRequired为true来启用事务作用域。

      ①.Customer

    CustomerContract
        [ServiceContract(SessionMode = SessionMode.Required)]
        
    public interface ICustomerContract
        {
            [OperationContract]
            [TransactionFlow(TransactionFlowOption.Allowed)]
            CustomerInfo Get(
    object id);

            [OperationContract]
            [TransactionFlow(TransactionFlowOption.Allowed)]
            
    object Save(CustomerInfo entity);

            [OperationContract]
            [TransactionFlow(TransactionFlowOption.Allowed)]
            
    void Update(CustomerInfo entity);
        }

        [AspNetCompatibilityRequirements(RequirementsMode 
    = AspNetCompatibilityRequirementsMode.Required)]
        
    public class CustomerServer : ICustomerContract
        {
            
    public ICustomerManager Manager { getset; }

            [OperationBehavior(TransactionScopeRequired 
    = true)]
            
    public CustomerInfo Get(object id)
            {
                
    return Manager.Get(id);
            }

            [OperationBehavior(TransactionScopeRequired 
    = true)]
            
    public object Save(CustomerInfo entity)
            {

                
    return Manager.Save(entity);
            }

            [OperationBehavior(TransactionScopeRequired 
    = true)]
            
    public void Update(CustomerInfo entity)
            {
                Manager.Update(entity);
            }

      ②.Order

      

    IOrderContract
        [ServiceContract(SessionMode = SessionMode.Required)]
        
    public interface IOrderContract
        {
            [OperationContract]
            [TransactionFlow(TransactionFlowOption.Allowed)]
            
    object Save(OrderInfo entity);
        }

       [AspNetCompatibilityRequirements(RequirementsMode 
    = AspNetCompatibilityRequirementsMode.Required)]
        
    public class OrderServer : IOrderContract
        {
            
    public IOrderManager Manager { getset; }

            [OperationBehavior(TransactionScopeRequired 
    = true)]
            
    public object Save(OrderInfo entity)
            {
                
    return Manager.Save(entity);
            }
        }

      2、配置

      然而,Spring.NET针对NHibernate的Session管理使用的是OSIV模式(Open Session In View),即使用httpModule去拦截HTTP请求,在每次请求开始时打开Session作用域(SessionScope),最后在请求结束后关闭SessionScope。这样一来,在客户端每请求一次时都会打开SessionScope,在请求结束会关闭SessionScope,然后当请求结束后再去处理分布式就会提示“无法使用已释放对象”的错误。所以说,OSIV是无法正常管理分布式事务的。出于上述原因,我们决定在Global.asax的配置,在Session(这里的Session是ASP.NET中的Session)启动时候打开SessionScope,在Session结束时关闭SessionScope。这样分布式事务就会与SessionScope同步了。

      最后,在配置appSettings节点增加
        <add key="Spring.Data.NHibernate.Support.SessionScope.SessionFactoryObjectName" value="NHibernateSessionFactory"/>

      另外配置WCF的binding时需要选择一种支持Session的binding(如wsHttpBinding)并且将binding中的transactionFlow属性设置为true。

      

    Global.asax
        public class Global : System.Web.HttpApplication
        {

            
    protected void Application_Start(object sender, EventArgs e)
            {
                log4net.Config.XmlConfigurator.Configure();
            }

            
    protected void Session_Start(object sender, EventArgs e)
            {
                SessionScope sessionScope 
    = new SessionScope("appSettings"typeof(SessionScope), false);
                sessionScope.Open();
                HttpContext.Current.Session[
    "SessionScope"= sessionScope;
            }

            
            
    protected void Session_End(object sender, EventArgs e)
            {
                SessionScope sessionScope 
    = HttpContext.Current.Session["SessionScope"as SessionScope;
                
    if (sessionScope != null)
                {
                    sessionScope.Close();
                }
            }

        }

      ①.Customer

     

    Web.config
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      
    ..............

      <!--spring配置-->
      
    <spring xmlns="http://www.springframework.net">
        
    <parsers>
          
    <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
          
    <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" />
        
    </parsers>
        
    <context>
          
    <resource uri="config://spring/objects" />

          
    <!--Dao-->
          
    <resource uri="assembly://Customer.Dao/Customer.Dao.Config/Dao.xml" />
          
    <!--Service-->
          
    <resource uri="assembly://Customer.Service/Customer.Service.Config/Service.xml" />

        
    </context>
        
    <objects xmlns="http://www.springframework.net"
                 xmlns:aop
    ="http://www.springframework.net/aop">

          
    <object id="Customer.Host" type="Customer.Host.Implement.CustomerServer, Customer.Host">
            
    <property name="Manager" ref="Customer.CustomerManager" />
          
    </object>

        
    </objects>
      
    </spring>

      
    <appSettings>
        
    <add key="Spring.Data.NHibernate.Support.SessionScope.SessionFactoryObjectName" value="NHibernateSessionFactory"/>
      
    </appSettings>

      
    <system.web>
        
    <compilation debug="true" targetFramework="4.0" />

        
    <httpModules>
          
    <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web" />
        
    </httpModules>

      
    </system.web>
      
    <system.serviceModel>
        
    <services>
          
    <service name="Customer.Host">
            
    <endpoint address="" binding="wsHttpBinding" bindingConfiguration="ServerBinding" contract="Customer.Host.ICustomerContract"/>
            
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
          
    </service>
        
    </services>
        
    <bindings>
          
    <wsHttpBinding >
            
    <binding name="ServerBinding" transactionFlow="true">
            
    </binding>
          
    </wsHttpBinding>
        
    </bindings>
        
    <behaviors>
          
    <serviceBehaviors>
            
    <behavior>
              
    <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 -->
              
    <serviceMetadata httpGetEnabled="true"/>
              
    <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 -->
              
    <serviceDebug includeExceptionDetailInFaults="true"/>
            
    </behavior>
          
    </serviceBehaviors>
        
    </behaviors>
        
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true"/>
      
    </system.serviceModel>
     
    <system.webServer>
        
    <modules runAllManagedModulesForAllRequests="true"/>
      
    </system.webServer>
      
    </configuration>
    <%@ ServiceHost Language="C#" Debug="true" Service="Customer.Host" Factory="Spring.ServiceModel.Activation.ServiceHostFactory"%>

      ②.Order

      

    Web.config
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>

      ..........

      
    <!--spring配置-->
      
    <spring xmlns="http://www.springframework.net">
        
    <parsers>
          
    <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
          
    <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" />
        
    </parsers>
        
    <context>
          
    <resource uri="config://spring/objects" />

          
    <!--Dao-->
          
    <resource uri="assembly://Order.Dao/Order.Dao.Config/Dao.xml" />
          
    <!--Service-->
          
    <resource uri="assembly://Order.Service/Order.Service.Config/Service.xml" />

        
    </context>
        
    <objects xmlns="http://www.springframework.net"
                 xmlns:aop
    ="http://www.springframework.net/aop">

          
    <object id="Order.Host" type="Order.Host.Implement.OrderServer, Order.Host">
            
    <property name="Manager" ref="Order.OrderManager" />
          
    </object>

        
    </objects>
      
    </spring>

      
    <appSettings>
        
    <add key="Spring.Data.NHibernate.Support.SessionScope.SessionFactoryObjectName" value="NHibernateSessionFactory"/>
      
    </appSettings>

      
    <system.web>
        
    <compilation debug="true" targetFramework="4.0" />

        
    <httpModules>
          
    <add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web" />
        
    </httpModules>

      
    </system.web>
      
    <system.serviceModel>
        
    <services>
          
    <service name="Order.Host">
            
    <endpoint address="" binding="wsHttpBinding" bindingConfiguration="ServerBinding" contract="Order.Host.IOrderContract"/>
            
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
          
    </service>
        
    </services>
        
    <bindings>
          
    <wsHttpBinding >
            
    <binding name="ServerBinding" transactionFlow="true"  >
            
    </binding>
          
    </wsHttpBinding>
        
    </bindings>
        
    <behaviors>
          
    <serviceBehaviors>
            
    <behavior>
              
    <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 -->
              
    <serviceMetadata httpGetEnabled="true"/>
              
    <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 -->
              
    <serviceDebug includeExceptionDetailInFaults="true"/>
            
    </behavior>
          
    </serviceBehaviors>
        
    </behaviors>
        
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true"/>
      
    </system.serviceModel>
     
    <system.webServer>
        
    <modules runAllManagedModulesForAllRequests="true"/>
      
    </system.webServer>
      
    </configuration>
    <%@ ServiceHost Language="C#" Debug="true" Service="Order.Host" Factory="Spring.ServiceModel.Activation.ServiceHostFactory"%>

      四、客户端

    HostTest
    [TestFixture]
        
    public class HostTest
        {
            
    private CustomerContractClient customerProxy;

            
    private OrderContractClient orderProxy;

            [SetUp]
            
    public void Init()
            {
                customerProxy 
    = new CustomerContractClient();
                orderProxy 
    = new OrderContractClient();
            }

            [Test]
            
    public void InitData()
            {
                
    using (TransactionScope scope = new TransactionScope())
                {
                    customerProxy.Save(
    new CustomerInfo
                    {
                        Name 
    = "刘冬"
                    });

                    scope.Complete();
                }
            }

            [Test]
            
    public void DistributedTransactionTest()
            {
                
    using (TransactionScope scope = new TransactionScope())
                {
                    
    try
                    {
                        CustomerInfo customer 
    = customerProxy.Get(1);
                        orderProxy.Save(
    new OrderInfo
                        {
                            Address 
    = "中国北京",
                            CustomerId 
    = (int)customer.ID,
                            OrderDate 
    = DateTime.Now
                        });
                        customer.Money 
    += 1000;
                        customerProxy.Update(customer);
                        scope.Complete();
                        Console.WriteLine(
    "分布式事务已提交");
                    }
                    
    catch (Exception ex)
                    {
                        Transaction.Current.Rollback();
                        Console.WriteLine(
    "发送错误:分布式事务已回滚");
                    }
                }
            }
        }
      

      五、运行效果

      1.初始化数据

      

      2.建立第一张订单,订金小于3000

      

      

      3.建立第一张订单,订金小于3000

     

      4.建立第一张订单,订金等于3000

     

      5.建立第一张订单,订金大于3000,事务回滚。

     

      好了,基于Web Service的分布式事务已经实现了。

      代码下载

      出处:http://www.cnblogs.com/GoodHelper/archive/2010/07/30/SpringNetDistributedTransaction2.html

      欢迎转载,但需保留版权。

  • 相关阅读:
    使用MobaXterm远程连接Ubuntu,启动Octave,界面不能正常显示
    ABP .Net Core 日志组件集成使用NLog
    ABP .Net Core Entity Framework迁移使用MySql数据库
    ABP前端使用阿里云angular2 UI框架NG-ZORRO分享
    阿里云 Angular 2 UI框架 NG-ZORRO介绍
    Visual Studio 2019 Window Form 本地打包发布猫腻
    VS Code + NWJS(Node-Webkit)0.14.7 + SQLite3 + Angular6 构建跨平台桌面应用
    ABP .Net Core 调用异步方法抛异常A second operation started on this context before a previous asynchronous operation completed
    ABP .Net Core To Json序列化配置
    .Net EF Core数据库使用SQL server 2008 R2分页报错How to avoid the “Incorrect syntax near 'OFFSET'. Invalid usage of the option NEXT in the FETCH statement.”
  • 原文地址:https://www.cnblogs.com/GoodHelper/p/SpringNetDistributedTransaction2.html
Copyright © 2011-2022 走看看