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
欢迎转载,但需保留版权。
原文链接:http://www.cnblogs.com/GoodHelper/archive/2010/07/30/SpringNetDistributedTransaction2.html