Spring.NET实用技巧3——NHibernate分布式事务(上)
在使用NHibernate作为持久层框架时,多数据库操作是一个比较难解决的问题。并且很多网友在给我发的eamil中经常谈到此问题。由于NHibernate是一种框架,不能像ADO.NET那样直接用SQL语句操作数据库,在动态改变DbConnection时比较麻烦,而且NHibernate目前并不完全支持多数据库,所以实现多数据库的操作是个棘手的问题。
回想一下,在使用ADO.NET实现多数据库的时候,无非是增加多个DbConnection,以后在每次事务结束后提交事务。所以说多数据库的实现难点在于实现分布式事务。那么,什么是分布式事务?分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。为了实现分布式事务,需要使用下面将介绍的两阶段提交协议。 阶段一:开始向事务涉及到的全部资源发送提交前信息。此时,事务涉及到的资源还有最后一次机会来异常结束事务。如果任意一个资源决定异常结束事务,则整个事务取消,不会进行资源的更新。否则,事务将正常执行,除非发生灾难性的失败。为了防止会发生灾难性的失败,所有资源的更新都会写入到日志中。这些日志是永久性的,因此,这些日志会幸免遇难并且在失败之后可以重新对所有资源进行更新。 阶段二:只在阶段一没有异常结束的时候才会发生。此时,所有能被定位和单独控制的资源管理器都将开始执行真正的数据更新。 在分布式事务两阶段提交协议中,有一个主事务管理器负责充当分布式事务协调器的角色。事务协调器负责整个事务并使之与网络中的其他事务管理器协同工作。 为了实现分布式事务,必须使用一种协议在分布式事务的各个参与者之间传递事务上下文信息,IIOP便是这种协议。这就要求不同开发商开发的事务参与者必须支持一种标准协议,才能实现分布式的事务。
由于Spring.NET框架的出现,便很好的解决了这一点——分布式事务处理(查询此博客)。TxScopePlatformTransactionManager由System.Transactions提供的本地/分布式的事务管理器。我们配置TxScopePlatformTransactionManager 则能够实现分布式事务处理。
下面,我建立两个数据库:一个数据库为Customer,用于存放客户资料数据;另一个数据库为Order,用于存放客户的订单数据。当某个客户增加订单成功时,则更新该客户的订金余额。我们做一个业务判断,该客户的余额不能超过3000,当超过3000时则抛出异常。
实现步骤如下:
一、启动MSDTC服务。运行->cmd输入->net start msdtc
二、代码实现
1.Domain
①.Customer
{
public virtual int? ID { get; set; }
public virtual string Name { get; set; }
public virtual decimal Money { get; set; }
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Customer.Domain" namespace="Customer.Domain">
<class name="CustomerInfo" table="T_Customer" 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>
②.Order
{
public virtual int? ID { get; set; }
public virtual int CustomerId { get; set; }
public virtual DateTime OrderDate { get; set; }
public virtual string Address { get; set; }
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Order.Domain" namespace="Order.Domain">
<class name="OrderInfo" table="T_Order" lazy="true" >
<id name="ID" column="ID" type="Int32" >
<generator class="native" />
</id>
<property name="CustomerId" type="int">
<column name="CustomerId"/>
</property>
<property name="OrderDate" type="DateTime">
<column name="OrderDate"/>
</property>
<property name="Address" type="string">
<column name="Address" length="200"/>
</property>
</class>
</hibernate-mapping>
2.Dao
①.Customer
{
CustomerInfo Get(object id);
object Save(CustomerInfo entity);
void Update(CustomerInfo entity);
}
public class CustomerDao : HibernateDaoSupport, ICustomerDao
{
public virtual object Save(CustomerInfo entity)
{
return this.HibernateTemplate.Save(entity);
}
public virtual CustomerInfo Get(object id)
{
return this.HibernateTemplate.Get<CustomerInfo>(id);
}
public void Update(CustomerInfo 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="Customer.DbProvider" provider="SqlServer-2.0"
connectionString="Server=.;database=Customer;uid=sa;pwd=;"/>
<object id="Customer.NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate21">
<property name="DbProvider" ref="Customer.DbProvider"/>
<property name="MappingAssemblies">
<list>
<value>Customer.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="Customer.HibernateTemplate" type="Spring.Data.NHibernate.Generic.HibernateTemplate">
<property name="SessionFactory" ref="Customer.NHibernateSessionFactory" />
<property name="TemplateFlushMode" value="Auto" />
<property name="CacheQueries" value="true" />
</object>
<!-- Dao -->
<object id="Customer.CustomerDao" type="Customer.Dao.Implement.CustomerDao,Customer.Dao">
<property name="HibernateTemplate" ref="Customer.HibernateTemplate"/>
</object>
</objects>
②.Order
{
object Save(OrderInfo entity);
}
public class OrderDao : HibernateDaoSupport, IOrderDao
{
public virtual object Save(OrderInfo entity)
{
return this.HibernateTemplate.Save(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="Order.DbProvider" provider="SqlServer-2.0"
connectionString="Server=.;database=Order;uid=sa;pwd=;"/>
<object id="Order.NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate21">
<property name="DbProvider" ref="Order.DbProvider"/>
<property name="MappingAssemblies">
<list>
<value>Order.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="Order.HibernateTemplate" type="Spring.Data.NHibernate.Generic.HibernateTemplate">
<property name="SessionFactory" ref="Order.NHibernateSessionFactory" />
<property name="TemplateFlushMode" value="Auto" />
<property name="CacheQueries" value="true" />
</object>
<!-- Dao -->
<object id="Order.OrderDao" type="Order.Dao.Implement.OrderDao,Order.Dao">
<property name="HibernateTemplate" ref="Order.HibernateTemplate"/>
</object>
</objects>
三、Service
{
void CreateOrder(OrderInfo order, decimal money);
object SaveCustomer(CustomerInfo customer);
}
public class OrderManager : IOrderManager
{
public ICustomerDao CustomerDao { get; set; }
public IOrderDao OrderDao { get; set; }
[Transaction]
public void CreateOrder(OrderInfo order,decimal money)
{
CustomerInfo customer = CustomerDao.Get(order.CustomerId);
OrderDao.Save(order);
if (customer.Money >= 3000)
{
throw new Exception("订金额度上限");
}
CustomerDao.Update(customer);
}
[Transaction]
public object SaveCustomer(CustomerInfo customer)
{
return CustomerDao.Save(customer);
}
}
<objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database"
xmlns:tx="http://www.springframework.net/tx">
<object id="transactionManager"
type="Spring.Data.Core.TxScopeTransactionManager, Spring.Data">
</object>
<!-- Service -->
<object id="Service.OrderManager" type="Service.Implement.OrderManager,Service">
<property name="CustomerDao" ref="Customer.CustomerDao"/>
<property name="OrderDao" ref="Order.OrderDao"/>
</object>
<tx:attribute-driven/>
</objects>
四、Test
public class ServiceTest
{
private IApplicationContext applicationContext;
[SetUp]
public void Init()
{
log4net.Config.XmlConfigurator.Configure();
applicationContext = ContextRegistry.GetContext();
}
[Test]
public void InitData()
{
CustomerInfo customer = new CustomerInfo
{
Name = "刘冬"
};
IOrderManager manager = (IOrderManager)applicationContext.GetObject("Service.OrderManager");
manager.SaveCustomer(customer);
}
[Test]
public void CreateOrderTest()
{
IOrderManager manager = (IOrderManager)applicationContext.GetObject("Service.OrderManager");
manager.CreateOrder(new OrderInfo
{
Address = "中国北京",
CustomerId = 1,
OrderDate = DateTime.Now
}, 1000);
}
}
<configuration>
<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>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
<section name="databaseSettings" type="System.Configuration.NameValueSectionHandler" />
</configSections>
<!--log4net配置-->
<log4net debug="true">
<appender name="LogFileAppender" type="log4net.Appender.FileAppender">
<param name="File" value="Logs\Log.log" />
<param name="datePattern" value="MM-dd HH:mm" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
</layout>
</appender>
<appender name="HttpTraceAppender" type="log4net.Appender.ASPNetTraceAppender">
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
</layout>
</appender>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
</layout>
</appender>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="Logs/Log.log" />
<param name="AppendToFile" value="true" />
<param name="MaxSizeRollBackups" value="10" />
<param name="MaximumFileSize" value="100K" />
<param name="RollingStyle" value="Size" />
<param name="StaticLogFileName" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="RollingLogFileAppender" />
</root>
</log4net>
<!--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" />
<resource uri="assembly://Order.Dao/Order.Dao.Config/Dao.xml" />
<!--Service-->
<resource uri="assembly://Service/Service.Config/Service.xml" />
</context>
<objects xmlns="http://www.springframework.net"/>
</spring>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
运行结果如下:
一、初始化数据:
二、建立第一个订单(插入第一次数据),订金小于3000
三、建立第一个订单(插入第二次数据),订金小于3000
四、建立第一个订单(插入第三次数据),订金等于3000
五、建立第一个订单(插入第四次数据),订金超过3000
从运行结果上看到,当我们创建第四张订单时,由于订金超过3000后抛出异常,数据实现回滚。这样分布式事务便实现了。
出处:http://www.cnblogs.com/GoodHelper/archive/2010/07/29/SpringNetDistributedTransaction1.html
欢迎转载,但需保留版权。