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 {  get set ; }

        
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 {  get set ; }

        
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 {  get set ; }

        [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 {  get set ; }

        [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

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


原文链接:http://www.cnblogs.com/GoodHelper/archive/2010/07/30/SpringNetDistributedTransaction2.html

posted @ 2012-07-08 12:56  attitudedecidesall  Views(353)  Comments(0Edit  收藏  举报