摘自: 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); }
publicclass CustomerManager : ICustomerManager { private ICustomerDao Dao { get; set; }
public CustomerInfo Get(object id) { return Dao.Get(id); }
publicobject Save(CustomerInfo entity) { return Dao.Save(entity); }
publicvoid Update(CustomerInfo entity) { if (entity.Money >3000) { thrownew 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
publicclass OrderManager : IOrderManager { public IOrderDao Dao { get; set; }
publicobject 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)] publicclass CustomerServer : ICustomerContract { public ICustomerManager Manager { get; set; }
[OperationBehavior(TransactionScopeRequired =true)] public CustomerInfo Get(object id) { return Manager.Get(id); }
[OperationBehavior(TransactionScopeRequired =true)] publicobject Save(CustomerInfo entity) {
return Manager.Save(entity); }
[OperationBehavior(TransactionScopeRequired =true)] publicvoid Update(CustomerInfo entity) { Manager.Update(entity); }
②.Order
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] publicclass OrderServer : IOrderContract { public IOrderManager Manager { get; set; }
[OperationBehavior(TransactionScopeRequired =true)] publicobject 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。
protectedvoid Application_Start(object sender, EventArgs e) { log4net.Config.XmlConfigurator.Configure(); }
protectedvoid Session_Start(object sender, EventArgs e) { SessionScope sessionScope =new SessionScope("appSettings", typeof(SessionScope), false); sessionScope.Open(); HttpContext.Current.Session["SessionScope"] = sessionScope; }
protectedvoid 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] publicvoid Init() { customerProxy =new CustomerContractClient(); orderProxy =new OrderContractClient(); }
[Test] publicvoid InitData() { using (TransactionScope scope =new TransactionScope()) { customerProxy.Save(new CustomerInfo { Name ="刘冬" });
scope.Complete(); } }
[Test] publicvoid 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
欢迎转载,但需保留版权。