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

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

posted @ 2010-07-30 14:44  冬子哥  阅读(7496)  评论(8编辑  收藏  举报