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