Spring.NET实用技巧5——WCF环境下的NHibernate分布式事务

  

  之前实现的NHibernate分布式事务,在WCF环境下遇到的一个难点,是NHibernate的Session管理。然而在我查看log4net生成的调试日志时候惊奇的发现,原来NHibernate的Session不一定需要SessionScope来管理。在遇到事务的时候能自动创建一个Session,在事务关闭的时候能自动关闭Session。SessionScope仅仅是把自动创建的Session合并为一个。就例如,在第一次调用服务层方法的时候会产生一个新的Session,在第二次调用的时候也会产生一个新的Session,在没有使用SessionScope把这两个请求“包围”起来的时候,由于是不同的Session,所以经常报“two session”这样的错。由此可见,作为分布式事务,会自动打开Session的,在分布式事务结束以后会关闭这个Session。同理,NHibernate延迟加载、持久化等机制也能很好的管理。这种Spring.NET给我们提供的Session管理机制就能很好的实现NHibernate分布式事务。

  

  让我们看一下Spring.NET提供的几种事务管理器

  

名称

作用

AdoPlatformTransactionManager

基于本地ADO.NET的事务

ServiceDomainPlatformTransactionManager

由企业服务提供的分布式事务管理器

TxScopePlatformTransactionManager

System.Transactions提供的本地/分布式的事务管理器

HibernateTransactionManager

基于NHibernate本地事务

 

 

  我们选择ServiceDomainPlatformTransactionManager或者TxScopePlatformTransactionManager即可以实现基于WCF环境下NHibernate的分布式事务。

 

 

  让我们模拟“银行转账”的场景:有两个银行,北京银行和上海银行。每个银行各有1个账户。先给北京银行的账户初始化1000元钱,然后北京银行的账户再向上海银行的账户转入1000元。接下来,向上海银行转入1000元钱,这时由于北京银行的账户余额不足,所以不能转入。

  我们为了能够测试分布式事务的效果,先将转入的账户加上转入的金额,然后再扣除转出账户的金额。当转出账户的金额不足时,转入账户的金额将会自动回滚。

 

  让我们看一下Demo。

  

  一、Domain

  

 

AccountInfo
    [DataContract]
    
public class AccountInfo
    {
        [DataMember]
        
public virtual int? ID { getset; }

        [DataMember]
        
public virtual string Name { getset; }

        [DataMember]
        
public virtual decimal Money { getset; }
    }

 

 

 

AccountInfo.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="BeiJing.Bank.Domain" namespace="BeiJing.Bank.Domain">
  
<class name="AccountInfo" table="T_Account" lazy="true" >

    
<id name="ID" column="ID" type="Int32" >
      
<generator class="native" />
    
</id>

    
<property name="Name" type="string">
      
<column name="Name" length="50"/>
    
</property>

    
<property name="Money" type="decimal">
      
<column name="Money" precision="16" scale="2"/>
    
</property>

  
</class>
</hibernate-mapping>

 

 

 

  二、Dao

     

AccountDao
    public interface IAccountDao
    {
        AccountInfo Get(
object id);

        
object Save(AccountInfo entity);

        
void Update(AccountInfo entity);
    }

    
public class AccountDao : HibernateDaoSupport, IAccountDao
    {
        
public virtual object Save(AccountInfo entity)
        {
            
return this.HibernateTemplate.Save(entity);
        }

        
public virtual AccountInfo Get(object id)
        {
            
return this.HibernateTemplate.Get<AccountInfo>(id);
        }

        
public void Update(AccountInfo entity)
        {
            
this.HibernateTemplate.Update(entity);
        }
    }

 

 

 

Dao.xml
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
         xmlns:db
="http://www.springframework.net/database">
 
  
<object type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core">
    
<property name="ConfigSections" value="databaseSettings"/>
  
</object>

  
<db:provider id="DbProvider" provider="SqlServer-2.0"
               connectionString
="Server=.;database=BeiJing;uid=sa;pwd=;"/>

  
<object id="NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate21">
    
<property name="DbProvider" ref="DbProvider"/>
    
<property name="MappingAssemblies">
      
<list>
        
<value>BeiJing.Bank.Domain</value>
      
</list>
    
</property>
    
<property name="HibernateProperties">
      
<dictionary>
        
<entry key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
        
<!--SqlServer连接-->
        
<entry key="dialect" value="NHibernate.Dialect.MsSql2000Dialect"/>
        
<entry key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>

        
<entry key="use_outer_join" value="true"/>
        
<entry key="show_sql" value="true"/>
        
<!--自动建表(反向映射)-->
        
<entry key="hbm2ddl.auto" value="update"/>
        
<!--批量更新-->
        
<entry key="adonet.batch_size" value="0"/>
        
<!--超时时间-->
        
<entry key="command_timeout" value="60"/>
        
<!--启用二级缓存-->
        
<entry key="cache.use_second_level_cache" value="false"/>
        
<!--启动查询缓存-->
        
<entry key="cache.use_query_cache" value="false"/>
        
<entry key="query.substitutions" value="true 1, false 0, yes 'Y', no 'N'"/>
        
<entry key="proxyfactory.factory_class" value="NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu"/>
      
</dictionary>
    
</property>
    
<property name="ExposeTransactionAwareSessionFactory" value="true" />
  
</object>

  
<object id="HibernateTemplate" type="Spring.Data.NHibernate.Generic.HibernateTemplate">
    
<property name="SessionFactory" ref="NHibernateSessionFactory" />
    
<property name="TemplateFlushMode" value="Auto" />
    
<property name="CacheQueries" value="true" />
  
</object>

  
<!-- Dao -->
  
<object id="AccountDao" type="BeiJing.Bank.Dao.Implement.AccountDao,BeiJing.Bank.Dao">
    
<property name="HibernateTemplate" ref="HibernateTemplate"/>
  
</object>

</objects>

 

 

 

  三、Service

 

AccountManager
    public interface IAccountManager
    {
        AccountInfo Get(
object id);

        
object Save(AccountInfo entity);

        
void Update(AccountInfo entity);
    }

    
public class AccountManager : IAccountManager
    {
        
public IAccountDao Dao { getset; }

        
public AccountInfo Get(object id)
        {
            
return Dao.Get(id);
        }

        
public object Save(AccountInfo entity)
        {
            
return Dao.Save(entity);
        }

        
public void Update(AccountInfo entity)
        {
            
if (entity.Money < 0)
            {
                
throw new Exception("账户金额不足");
            }
            Dao.Update(entity);
        }
    }

 

 

 

Service.xml
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
         xmlns:tx
="http://www.springframework.net/tx">

  
<object id="transactionManager"
      type
="Spring.Data.Core.TxScopeTransactionManager, Spring.Data">
  
</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="AccountManager" parent="BaseTransactionManager">
    
<property name="Target">
      
<object type="BeiJing.Bank.Service.Implement.AccountManager, BeiJing.Bank.Service">
        
<property name="Dao" ref="AccountDao"/>
      
</object>
    
</property>
  
</object>
  
</objects>

 

 

 

  四、Host

  

Contract
    [ServiceContract(SessionMode = SessionMode.Required)]
    
public interface IContract
    {
        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        AccountInfo Get(
object id);

        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        
object Save(AccountInfo entity);

        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        
void Update(AccountInfo entity);
    }

    [AspNetCompatibilityRequirements(RequirementsMode 
= AspNetCompatibilityRequirementsMode.Required)]
    
public class BankServer : IContract
    {
        
public IAccountManager Manager { getset; }

        [OperationBehavior(TransactionScopeRequired 
= true)]
        
public AccountInfo Get(object id)
        {
            
return Manager.Get(id);
        }

        [OperationBehavior(TransactionScopeRequired 
= true)]
        
public object Save(AccountInfo entity)
        {
            
return Manager.Save(entity);
        }

        [OperationBehavior(TransactionScopeRequired 
= true)]
        
public void Update(AccountInfo entity)
        {
            Manager.Update(entity);
        }
    }

 

 

 

<%@ ServiceHost Language="C#" Debug="true" Service="Host" Factory="Spring.ServiceModel.Activation.ServiceHostFactory"%>

 

 

 

web.config
  <configSections>

    
<sectionGroup name="spring">
      
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      
<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/>
      
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
    
</sectionGroup>


  
</configSections>


......


<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://BeiJing.Bank.Dao/BeiJing.Bank.Dao.Config/Dao.xml" />
      
<!--Service-->
      
<resource uri="assembly://BeiJing.Bank.Service/BeiJing.Bank.Service.Config/Service.xml" />

    
</context>
    
<objects xmlns="http://www.springframework.net">

      
<object id="Host" type="BeiJing.Bank.Host.Implement.BankServer, BeiJing.Bank.Host">
        
<property name="Manager" ref="AccountManager" />
      
</object>

    
</objects>
  
</spring>


.......

<system.serviceModel>
    
<services>
      
<service name="Host">
        
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="ServerBinding" contract="BeiJing.Bank.Host.IContract"/>
        
<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>

 

 

  五、Test

  

TransactionTest
    [TestFixture]
    
public class TransactionTest
    {
        
private BeiJingProxy.ContractClient beiJingProxy;

        
private ShangHaiProxy.ContractClient shangHaiProxy;

        [SetUp]
        
public void Init()
        {
            beiJingProxy 
= new BeiJingProxy.ContractClient();
            shangHaiProxy 
= new ShangHaiProxy.ContractClient();
        }

        [Test]
        
public void InitData()
        {
            beiJingProxy.Save(
new BeiJingProxy.AccountInfo
            {
                Name 
= "刘冬",
                Money 
= 1000
            });

            shangHaiProxy.Save(
new ShangHaiProxy.AccountInfo
            {
                Name 
= "冬冬",
                Money 
= 0
            });
        }
        
        [Test]
        
public void CompleteTest()
        {
            
using (TransactionScope scope = new TransactionScope())
            {
                var beiJingAccount 
= beiJingProxy.Get(1);
                var shangHaiAccount 
= shangHaiProxy.Get(1);

                beiJingAccount.Money 
-= 1000;
                shangHaiAccount.Money 
+= 1000;

                shangHaiProxy.Update(shangHaiAccount);
                beiJingProxy.Update(beiJingAccount);

                scope.Complete();
            }
        }

 

 

 

  六、运行效果

 

  1.初始化数据

  

 

  2.第一次转账:北京账户转入上海账户1000元

  

 

  3.第二次转账:北京账户转入上海账户1000元,由于北京账户余额不足,所以上海账户增加的1000元回滚。

  

  

 

 

 

  好了,基于WCF环境下的NHibernate分布式事务就完美的实现了。

  

 

  代码下载

  出处:http://www.cnblogs.com/GoodHelper/archive/2010/08/04/SpringNetWcfDistributedTransaction.html

  欢迎转载,但需保留版权。

posted @ 2010-08-12 13:45  冬子哥  阅读(8338)  评论(10编辑  收藏  举报