post

iBatis异常问题处理

今天碰到个异常,异常内容为:IBatisNet.DataMapper.Exceptions.DataMapperException: SqlMap could not invoke BeginTransaction(). A Transaction is already started. Call CommitTransaction() or RollbackTransaction first.

反复查找资料,最后同事在官方论坛上看到类似问题。先来看问题代码:

try{
 using (IDalSession session = Writer.BeginTransaction())
 {
    //处理逻辑
    //处理成功,提交事务
    session.Complete();
 }
}
catch (Exception ex)
{
    throw ex;
}

此段代码问题在于,using的使用,如果writer的内部已经创建了IDalSession 的对象,但是
BeginTransaction()打开数据库连接的时候超时或者失败,那么代码中session 就为空,using没用起到任何作用,即不会销毁对象。
这个时候,下一次执行这个方法的时候,LocalSession 对象就一直不为空,就会出现上面的异常信息,异常信息的源码为:

public IDalSession BeginTransaction()
{
       if (_sessionHolder.LocalSession != null)
       {
       throw new DataMapperException(
        "SqlMap could not invoke BeginTransaction(). A Transaction is
         already started. Call CommitTransaction() or
RollbackTransaction first.");
       }
       ..........
}

解决方法为:

在catch内部,将Writer.CloseConnection();

p.s.上述方法不能解决问题的话,建议使用标准的写法:

try{
 Writer.BeginTransaction();
 //code
 Writer.CommitTransaction();
}catch(Exception ex){
 Writer.RollBackTransaction();
}

以下为论坛原文,上面是我的理解,如果有误还请指教;

I’m using Data Access with SQL Mapper.

You can see the example in the Data Access Guilde 1.9.1(and lower edition) Chapter5 – 5-5 – Working with Connection and Transactions – Example 5.6

using ( IDalSession session = daoManager.OpenConnection() )
 {
 Account account = NewAccount();
 accountDao.Create(account);
 }
 using ( IDalSession session = daoManager.BeginTransaction() )
 {
 Account account = NewAccount();
 Account account2 = accountDao.GetAccountById(1);
 account2.EmailAddress = "someotherAddress@somewhere.com";
 accountDao.Create(account);
 accountDao.Update(account2);
 session.Complete(); // Commit
 }

The example code above is not kind enough. If daoManager.OpenConnection() or daoManager.BeginTransaction() fails due to network trouble or connection timeout or something, the instance of IDalsession is null, so Dispose method is not called. This leaves SqlMapSession remain in the ISessionStore(CallContextSessionStore) and the next transactions all fails to produce an exception like below even if recovered from network trouble. IBatisNet.DataMapper.Exceptions.DataMapperException: SqlMap could not invoke BeginTransaction(). A Transaction is already started. Call CommitTransaction() or RollbackTransaction first. You can reproduce this result by writing iteration code in which “using syntax transaction” exists.

foreach(string key in keyList)
 {
 try
 {
 // call transaction method which includes the code lilke Example 5.6 here
 }
 catch (Exception e)
 {
 Console.WriteLine(e.ToString());
 Console.WriteLine(e.StackTrace);
 // continue next
 }
 }

My workaround so far is to call daoManager.CloseConnection() in the catch block like below and it works in sqlServer1.1 provider and oracleClient1.0 provider.

try
 {
 using ( IDalSession session = daoManager.OpenConnection() )
 {
 Account account = NewAccount();
 accountDao.Create(account);
 }
 }
 catch (Exception ex)
 {
 daoManager.CloseConnection();
 throw new RDBAccessException("message",ex);
 }

try
 {
using ( IDalSession session = daoManager.BeginTransaction() )
 {
 Account account = NewAccount();
 Account account2 = accountDao.GetAccountById(1);
 account2.EmailAddress = "someotherAddress@somewhere.com";
 accountDao.Create(account);
 accountDao.Update(account2);
 session.Complete(); // Commit
 }
 }
 catch (Exception ex)
 {
 daoManager.CloseConnection();
 throw new RDBAccessException("message",ex);
 }

I think it will be better to describe this matter in the document.
If you have the other better way please add it to the document.

Thanks.

  1. 衡哥给力啊!其实还可以把try 放在 using外面,然后 如果在提交过程中发现问题直接就return false 也可以搞定

  2. 衡哥给力啊!其实还可以把try 放在 using外面,然后 如果在提交过程中发现问题直接就return false 也可以搞定

· 2,409 次浏览