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

长平狐 发布于 2012/06/11 11:54
阅读 906
收藏 0

之前实现的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 {  get set ; }

        [DataMember]
        
public   virtual   string  Name {  get set ; }

        [DataMember]
        
public   virtual   decimal  Money {  get set ; }
    }

 

 

 

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

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

        [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

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


原文链接:http://www.cnblogs.com/GoodHelper/archive/2010/08/12/SpringNetWcfDistributedTransaction.html
加载中
返回顶部
顶部