摘自: http://www.cnblogs.com/GoodHelper/archive/2010/07/30/SpringNetDistributedTransaction2.html
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


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); } }


< 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


public class OrderManager : IOrderManager { public IOrderDao Dao { get ; set ; }
public object Save(OrderInfo entity) { return Dao.Save(entity); } }


< 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


[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


[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。


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


<!-- 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 >
②.Order


..........
<!-- 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 >
四、客户端


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
欢迎转载,但需保留版权。