[转载]Spring.NET实用技巧4——NHibernate分布式事务(下)

[转载]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 { 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);
}
}

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 { get; set; }

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 { 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

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

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的分布式事务已经实现了。

代码下载

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏