实践篇(3)--关于事务处理的一点细节
很早就注意到了微软的MTS COM+,但是却很少使用,主要原因是因为它的第一次执行太慢,耗费的资源比较多。在服务器配置比较乐观的项目里,比如某移动公司和某知名跨国日化生产商的系统里,恰当地使用COM+反而使得整体负载和程序的框架变得很清晰。
在castle中集成了NHibernateIntegration对NHibernate进行了集成,对Session和Transaction进行了很好的封装。但是在对发布的beta版进行了最基本的测试后,我发现这个开源的东西还比较简陋。于是通过SVN获取了最新的开发版本,发现已经完全重写了。所以我就一个经典的转账来看看他们各有怎样的表现.
测试场景:
我们假设一个娱乐中心要通过银行把钱转给在本中心娱乐的那些玩家中的获奖者,娱乐中心的账号设在bank1;而获奖者的账号都在bank2. 以下是转账的规则:
1) 娱乐中心没有钱肯定不能转帐;
2) 一次转账额不能超过5000;
3) 银行账号不能有重复;
在很多的经典例子中,都太简单,这里我们模拟在每次都转到新开账号上,如果对第二次转帐到同一玩家,您讲看到一个异常。我们看看这两个容器是否能经受考验!
以下就是针对COM+ 的程序片断:
using System.Data;
using System.Reflection;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
using Dao;
[assembly: ApplicationName("COM+ demo")]
[assembly: ApplicationActivation(ActivationOption.Library)]
namespace Services {
[Transaction(TransactionOption.Required)]
[ObjectPooling(Enabled=true,MinPoolSize=2,MaxPoolSize=4)]
public class MyService:ServicedComponent {
public MyService() {
Console.WriteLine(Assembly.GetExecutingAssembly().FullName);
}
[AutoComplete]
public virtual void Transfer( string from_BankTag,string from_uid,
string to_BankTag ,string to_uid,
int money) {
if(money<0) return ;
Withdraw(from_BankTag,from_uid,money);
CreateNewAccount(to_BankTag,to_uid,money);
}
[AutoComplete]
public virtual void Withdraw(string db, string uid,int money) {
AccountDAO dao=new AccountDAO(db);
BankDataSet.AccountsRow acc=dao.getAccount(uid);
if(acc==null) throw new Exception("不存在!"+uid);
if(acc.Balance<money ) throw new Exception("余额不足!");
if (5000<money) throw new Exception("一次不能超过:"+5000);
acc.Balance-=money;
dao.saveTable(acc.Table);
}
[AutoComplete]
public void InitDbs() {
AccountDAO dao1=new AccountDAO("demo1");
AccountDAO dao2=new AccountDAO("demo2");
dao1.cleanTable();
dao2.cleanTable();
CreateNewAccount("demo1","alex",10000);
CreateNewAccount("demo2","cx",0);
}
public int getBalance(string db,string uid) {
AccountDAO dao=new AccountDAO(db);
Dao.BankDataSet.AccountsRow acc= dao.getAccount(uid);
if(acc==null) return 0;
return acc.Balance;
}
private void CreateNewAccount(string db,string uid,int money) {
AccountDAO dao=new AccountDAO(db);
BankDataSet.AccountsDataTable tbl=dao.getScheme();
BankDataSet.AccountsRow r=tbl.NewAccountsRow();
r.ID=Guid.NewGuid().ToString();
r.UserCode=uid;
r.Balance=money;
tbl.Rows.Add(r);
dao.saveTable(tbl);
}
}
}
在MTS里运行COM+,您的相关组件必须使用强名称,请注意要把
[assembly: AssemblyVersion("1.0.0.0")] 明确设定,否则每次运行在组件管理其中都会产生新的版本。
我们再看看NHibernateIntegration的表现如何:
using System.Collections;
using Demo.Entities;
using Castle.Services.Transaction;
using Castle.Facilities.NHibernateIntegration;
using NHibernate;
using NHibernate.Expression;
namespace Demo {
[Transactional]
public class AccountDao:NHibernateGenericDao {
private readonly ISessionManager sessManager;
private readonly string dbTag;
public AccountDao(ISessionManager sessManager):base(sessManager) {
this.sessManager = sessManager;
this.dbTag=Constants.DefaultAlias;
}
public AccountDao(ISessionManager sessManager,string DbTag):base(sessManager) {
this.sessManager = sessManager;
this.dbTag=DbTag;
}
[Transaction(TransactionMode.Requires)]
public virtual void Deposit( string uid,int money) {
using(ISession session = sessManager.OpenSession(dbTag)) {
Account acc=getAccount(session,uid);
acc.Balance+=money;
session.Save(acc);
//session.Flush();
}
}
private int getMaxMoney() {
return 5000;
}
public Account getAccount(string uid) {
using(ISession session = sessManager.OpenSession(dbTag)) {
IList list=session.CreateCriteria(typeof(Account)).Add(
new NHibernate.Expression.EqExpression("UserId",uid)).List();
if(list!=null&&list.Count>0) return list[0] as Account;
return null;
}
}
private Account getAccount(ISession session,string uid) {
IList list=session.CreateCriteria(typeof(Account)).Add(
new NHibernate.Expression.EqExpression("UserId",uid)).List();
// Account[] accs=this.FindAllWithCustomQuery("from Account where UserId='"+uid+"'") as Account[];
if(list==null||list.Count<1) return null;//throw new Exception("帐户不存在!");
return list[0] as Account;
}
public virtual int getBalance( string uid) {
using(ISession session = sessManager.OpenSession(dbTag)) {
Account acc=getAccount(session,uid);
return acc.Balance;
}
}
[Transaction(TransactionMode.Requires)]
public virtual void CrateNewAccount( string uid,int money) {
using(ISession session = sessManager.OpenSession(dbTag)) {
Account acc=new Account();
acc.UserId=uid;
acc.Balance=money;
session.Save(acc);
session.Flush();
//没有上面这句提交事务时发生问题,但也无法回滚了。
}
}
[Transaction(TransactionMode.Requires)]
public virtual void Withdraw( string uid,int money) {
using(ISession session = sessManager.OpenSession(dbTag)) {
Account acc=getAccount(session,uid);
if(acc.Balance<money ) throw new Exception("余额不足!");
if (getMaxMoney()<money) throw new Exception("一次不能超过:"+getMaxMoney());
acc.Balance-=money;
session.Save(acc);
session.Flush();
}
}
}
}
而上层的服务代码如下:
using System.Collections;
using Demo.Entities;
using NHibernate;
using Castle.MicroKernel;
using Castle.Services.Transaction;
namespace Demo {
/// <summary>
/// BlogDao 的摘要说明。
/// </summary>
[Transactional]
public class BankService {
private Castle.MicroKernel.IKernel _container;
public BankService(IKernel k) {
_container=k;
}
[Transaction(TransactionMode.Requires)]
public virtual void Transfer( string from_BankTag,string from_uid,
string to_BankTag ,string to_uid,
int money) {
if(money<0) return ;
AccountDao fromDao=_container[from_BankTag] as AccountDao;
AccountDao toDao=_container[to_BankTag] as AccountDao;
fromDao.Withdraw(from_uid,money);
toDao.CrateNewAccount(to_uid,money);//演示提交事务时发生问题
// Account acc=toDao.getAccount(to_uid);
// if(acc==null) toDao.CrateNewAccount(to_uid,0);
// toDao.Deposit(to_uid,money);
}
}
}
<configuration>
<facilities>
<facility id="nhibernate">
<!-- 数据库一 -->
<factory id="demo1">
<settings>
<item key="hibernate.connection.provider">NHibernate.Connection.DriverConnectionProvider</item>
<item key="hibernate.connection.driver_class">NHibernate.Driver.SqlClientDriver</item>
<item key="hibernate.connection.connection_string">Server=localhost;Database=demo1;Uid=sa;Pwd=</item>
<item key="hibernate.dialect">NHibernate.Dialect.MsSql2000Dialect</item>
</settings>
<resources>
<resource name="./Account.hbm.xml" />
</resources>
</factory>
<!-- 数据库二 -->
<factory id="demo2" alias="db2">
<settings>
<item key="hibernate.connection.provider">NHibernate.Connection.DriverConnectionProvider</item>
<item key="hibernate.connection.driver_class">NHibernate.Driver.SqlClientDriver</item>
<item key="hibernate.connection.connection_string">Server=localhost;Database=demo2;Uid=sa;Pwd=</item>
<item key="hibernate.dialect">NHibernate.Dialect.MsSql2000Dialect</item>
</settings>
<resources>
<resource name="./Account.hbm.xml" />
</resources>
</factory>
</facility>
</facilities>
<components>
<component id="bank2" >
<parameters>
<DbTag>db2</DbTag>
</parameters>
</component>
</components>
</configuration>
通过改造NHibernateIntegration,我们可以在输出窗口看到执行过程:
1) 启动Transfer事务;
2) 启动Withdraw事务;
3) 完成Withdraw处理;
4) 启动CrateNewAccount处理;
5) 完成CrateNewAccount处理;
6) 对bank1和bank2进行数据库事务提交;
7) 完成Transfer事务;
结论:
首次执行COM+明显慢一些;
COM+的逻辑没有发生混乱;而NHibernateIntegration在没有及时进行缓存刷新时发生了严重的逻辑错误并且不能回滚。
另外我在一些有递归的情况下进行测试发现:使用延迟加载会出现session closed 或者访问空值得异常情况。
让我们期待NHibernateIntegration的完善吧......
alex