ibatis-user-java mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Barnett, Brian W." <brian.barn...@pearson.com>
Subject RE: Transaction question
Date Wed, 24 Aug 2005 15:51:08 GMT
Well, I decided to just encapsulate the desired functionality on my own
rather than mess with the iBATIS code.

package com.whitesandsolutions.base;

import com.ibatis.dao.client.DaoManager;

public class TransactionManager {
	private static class ThreadLocalTXCount extends ThreadLocal {
		public Object initialValue() {
			return new Integer(0);
		}
	}

	private ThreadLocalTXCount txCount = new ThreadLocalTXCount();

	public void startTransaction(DaoManager daoManager) {
		Integer counter = (Integer) txCount.get();
		counter = new Integer(counter.intValue() + 1);
		txCount.set(counter);
		if (counter.intValue() == 1) {
			daoManager.startTransaction();
		}
	}
	  
	public void commitTransaction(DaoManager daoManager) {
		Integer counter = (Integer) txCount.get();
	    if (counter.intValue() == 1) {
	    	daoManager.commitTransaction();
	    }
	}

	public void endTransaction(DaoManager daoManager) throws
ServiceException {
		Integer counter = (Integer) txCount.get();
		try {
			if (counter.intValue() == 1) {
				daoManager.endTransaction();
			}
		} finally {
			counter = new Integer(counter.intValue() - 1);
			if (counter.intValue() < 0) {
				throw new ServiceException("Programmer
error... Blah blah");
			}
			txCount.set(counter);
		}
	}
}

I have a BaseService class which all my service layer classes extend. The
BaseService class houses the TransactionManager and has some helper methods,
startTransaction(), commitTransaction() and endTransaction(), which simply
call the corresponding TransactionManager methods, passing the daoManager
object in.

	private static TransactionManager transactionManager = new
TransactionManager();
	protected static DaoManager daoManager = DaoConfig.getDaoManager();

	public void startTransaction() {
		transactionManager.startTransaction(daoManager);
	}
	
	public void commitTransaction() {
		transactionManager.commitTransaction(daoManager);
	}
	
	public void endTransaction() throws ServiceException {
		transactionManager.endTransaction(daoManager);
	}
	
The methods in my service layer classes can now do something like this and
not be worried about any transaction nesting that I want to do:

	public int updateUser(UserDTO user) throws ServiceException {
		int retVal = 0;
		try {
			startTransaction();
			UserDaoInterface userDao = (UserDaoInterface)
daoManager.getDao(UserDaoInterface.class);
			retVal = userDao.updateUser(user);
			// Call other service level methods here that
manipulate data and everything is still
			// in one transaction.
			commitTransaction();
		} catch (Exception e) {
			ServiceException se = new ServiceException(e);
			se.addException(e);
			se.setMessageKey("errors.service.data");

			Object[] args = { "updating", "user", e.toString()
};
			se.setMessageArgs(args);
			throw se;
		} finally {
			endTransaction();
		}

		return retVal;
	}	

I've done some minimal testing, and it appears to be working.


-----Original Message-----
From: Barnett, Brian W. 
Sent: Thursday, August 18, 2005 9:51 AM
To: 'user-java@ibatis.apache.org'
Subject: RE: Transaction question


Niels,
Thanks for the info. Maybe I am not totally understanding how your modified
code works, but is it meant to handle nested transactions?

If a transaction has already been started, then your modified code will
simply call invoke. I understand that. What happens when a nested
transaction calls commitTransaction()? (Like doSomethingElse() calling
doSomething() in the sample code below.) commitTransaction() gets called
twice in this example. I believe it will throw an exception.

What I was considering was to add a ThreadLocal transactionCounter variable
to DaoContext. start, commit and end transaction methods might look
something like this:

  public void startTransaction() {
    transactionCounter++;          // <-- NEW LINE OF CODE
    if (transactionCounter == 1) { // <-- NEW LINE OF CODE
      if (state.get() != DaoTransactionState.ACTIVE) {
        DaoTransaction trans = transactionManager.startTransaction();
        transaction.set(trans);
        state.set(DaoTransactionState.ACTIVE);
        daoManager.addContextInTransaction(this);
      }
    }                              // <-- NEW LINE OF CODE
  }

  public void commitTransaction() {
    if (transactionCounter == 1) { // <-- NEW LINE OF CODE
      DaoTransaction trans = (DaoTransaction) transaction.get();
      if (state.get() == DaoTransactionState.ACTIVE) {
        transactionManager.commitTransaction(trans);
        state.set(DaoTransactionState.COMMITTED);
      } else {
        state.set(DaoTransactionState.INACTIVE);
      }
    }                              // <-- NEW LINE OF CODE
  }

  public void endTransaction() {
    try {                            // <-- NEW LINE OF CODE
      if (transactionCounter == 1) { // <-- NEW LINE OF CODE
        DaoTransaction trans = (DaoTransaction) transaction.get();
        if (state.get() == DaoTransactionState.ACTIVE) {
          try {
            transactionManager.rollbackTransaction(trans);
          } finally {
            state.set(DaoTransactionState.ROLLEDBACK);
            transaction.set(null);
          }
        } else if (transactionCounter == 1) {
          state.set(DaoTransactionState.INACTIVE);
          transaction.set(null);
        }
      }                     // <-- NEW LINE OF CODE
    } finally {             // <-- NEW LINE OF CODE
      transactionCounter--; // <-- NEW LINE OF CODE
    }                       // <-- NEW LINE OF CODE
  }

The idea being that the transaction related logic only executes when we are
dealing with the first transaction bracket that was opened. I haven't
actually tried this out, but I think I will. Let me know if you, or anyone
else, sees any problems with this approach.

Thanks,
Brian Barnett

-----Original Message-----
From: Niels Beekman [mailto:n.beekman@wis.nl] 
Sent: Wednesday, August 17, 2005 3:00 PM
To: user-java@ibatis.apache.org
Subject: RE: Transaction question


Hi,

I'm facing this exact same problem however in a somewhat different context,
see the following archived thread:

http://www.mail-archive.com/ibatis-user-java@incubator.apache.org/msg025
80.html

http://www.mail-archive.com/user-java@ibatis.apache.org/msg00036.html

I recently restarted my investigation into this problem and have tried some
hacks in the iBATIS code, the changes were made in DaoProxy.java (which
proxies DAO-interfaces to provide transaction-semantics) and DaoContext.java
(which handles the transactions itself). Of course this is rather messy, but
I really do not like the SavePoint-support mentioned in the thread above, I
think it is rather a workaround than a solution.

Anyway, my changes (totally unverified, without any guarantees) in package
com.ibatis.dao.engine.impl:

DaoContext.java, added isTransactionRunning():

public boolean isTransactionRunning() {
  return transaction.get() != null;
}

DaoProxy.java, modified invoke(): see attached file.

This seems to work pretty good in my case, however further investigation is
required.

I hope the iBATIS devteam can comment on my solution, whether you think it
will work, or when you believe it really sucks :)

Greetings,

Niels

-----Original Message-----
From: Barnett, Brian W. [mailto:brian.barnett@pearson.com] 
Sent: woensdag 17 augustus 2005 22:00
To: 'user-java@ibatis.apache.org'
Subject: Transaction question

What are some good options to deal with the following:

ServiceClass1
public void doSomething() {
	try {
		daoManager.startTransaction();
		// Write some stuff to a database
		daoManager.commitTransaction();
	} catch (Exception e) {
		throw e;
	} finally {
		daoManager.endTransaction();
	}
}

ServiceClass2
public void doSomethingElse() {
	try {
		daoManager.startTransaction();
		ServiceClass1 sc1 = new ServiceClass1();
		sc1.doSomething();
		// Write some stuff to a database
		daoManager.commitTransaction();
	} catch (Exception e) {
		throw e;
	} finally {
		daoManager.endTransaction();
	}
}

The doSomethingElse() method will fail because startTransaction() gets
called twice. I need a good way to be able to re-use business logic methods
in different, or the same, service layer classes.

One thing I have done in the past is create a whole new layer, essentially a
service delegate, where I have moved all the transaction logic to. I didn't
like that too much because it was just a huge proliferation of methods just
to solve the "no nested transaction" problem.

I have also passed flags into methods to indicate if a transaction has
already been started, but passing flags is messy to say the least.

Any great ideas out there?

TIA,
Brian Barnett

************************************************************************
**** 
This email may contain confidential material. 
If you were not an intended recipient, 
Please notify the sender and delete all copies. 
We may monitor email to and from our network. 
************************************************************************
****

****************************************************************************

This email may contain confidential material. 
If you were not an intended recipient, 
Please notify the sender and delete all copies. 
We may monitor email to and from our network. 
****************************************************************************

**************************************************************************** 
This email may contain confidential material. 
If you were not an intended recipient, 
Please notify the sender and delete all copies. 
We may monitor email to and from our network. 
****************************************************************************

Mime
View raw message