之前实现的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


public class AccountInfo
{
[DataMember]
public virtual int ? ID { get ; set ; }
[DataMember]
public virtual string Name { get ; set ; }
[DataMember]
public virtual decimal Money { get ; set ; }
}


< 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


{
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);
}
}


< 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


{
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);
}
}


< 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


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);
}
}


< 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


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
欢迎转载,但需保留版权。