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
{
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);
}
}
<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
{
object Save(OrderInfo entity);
}
public class OrderManager : IOrderManager
{
public IOrderDao Dao { get; set; }
public object Save(OrderInfo entity)
{
return Dao.Save(entity);
}
}
<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
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
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。
{
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
<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>
②.Order
<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>
四、客户端
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
欢迎转载,但需保留版权。