public void Save(T entity)
{ bool manageTransaction = ! orm.isActiveTransaction; if (manageTransaction) { orm.BeginTransaction(IsolationLevel.ReadCommitted);}
try {orm.session.SaveOrUpdate(entity);
if (manageTransaction) {orm.CommitTransaction();
}
}catch(Exception e)
{ if (manageTransaction) {orm.RollbackTransaction();
}
throw e;}
}
Almost 90% of the code is for transaction management. It will be nice if we can write something like:
[Transaction(IsolationLevel.ReadCommitted)]
public void Save(T entity)
{mOrm.session.SaveOrUpdate(entity);
mOrm.session.Flush();
}
To achieve this, I will use Spring.NET to intercept method call of the DAO. Before method marked with TransactionAttribute is executed a transaction will be started and then committed after method is executed. Although .NET provides solution for intercepting method calls via ContextBoundObject, I will use Spring.NET because this way my DAO object must implement just one interface instead of inheriting from ContextBoundObject.
Lets begin with the implementation. First I will declare the attribute used to mark transaction sensitive methods:
[AttributeUsage(AttributeTargets.Method)]
public class TransactionAttribute : Attribute
{private IsolationLevel mIsolationLevel;
public TransactionAttribute() { mIsolationLevel = IsolationLevel.ReadCommitted;}
public TransactionAttribute(IsolationLevel aIsolation)
{mIsolationLevel = aIsolation;
}
public IsolationLevel isolationLevel
{get { return mIsolationLevel; }
set { mIsolationLevel = value; }
}
}
Now I will decrare the class responsible for starting and committing transactions. There are a lot of specific issues about method interception and Spring.NET so if you are not familiar with them read the documentation about Spring.NET. The class looks like:
public class TransactionAroundAdvice : IMethodInterceptor
{ OrmManager mOrm; public TransactionAroundAdvice() { mOrm = OrmManagerFactory.GetInstance();}
private TransactionAttribute GetTransactionAttribute(MethodInfo aMethod)
{ object[] attributes = aMethod.GetCustomAttributes(typeof(TransactionAttribute), true);
if (attributes.Length != 1) { //TODO: Throw more meaningfull exception and messagethrow new Exception("Transaction Attribute is missing");
}
return (TransactionAttribute)attributes[0];
}
public object Invoke(IMethodInvocation invocation)
{ bool manageTransaction = ! mOrm.isActiveTransaction; if (manageTransaction) { TransactionAttribute transactionAttribute =GetTransactionAttribute(invocation.Method);
mOrm.BeginTransaction(transactionAttribute.isolationLevel);
}
//TODO: Find way to compare isolation level from TransactionAttribute and //current active transaction if manageTransaction == false object returnValue; try {returnValue = invocation.Proceed();
if (manageTransaction) {mOrm.CommitTransaction();
}
}catch(Exception e)
{ if (manageTransaction) {mOrm.RollbackTransaction();
}
if ((e is TargetInvocationException) && (e.InnerException != null))
{ //Because when AOP is enables, method is invoked via reflection //TargetInvocationException is thrown. This exception is most likely //to confuse application developer, so it is removed throw e.InnerException;}
else { throw e;}
}
return returnValue;}
}
When a method marked with TransactionAttribute is intercepted then Invoke method from TransactionAroundAdvice class is called. Now lets see how our DAO class looks like when transaction management code is removed:
public class AttributeEnabledDAO<T>: IDAO<T>
{ public AttributeEnabledDAO() { mOrm = OrmManagerFactory.GetInstance();}
private OrmManager mOrm;
#region IDAO Memberspublic T Load(object id)
{return (T) mOrm.session.Load(typeof(T), id);
}
[Transaction(IsolationLevel.ReadCommitted)]
public void Save(T entity)
{mOrm.session.SaveOrUpdate(entity);
mOrm.session.Flush();
}
#endregion}
Much more simple. Example of using AttributeEnabledDAO is given bellow:
ProxyFactory articleDAOFactory = new ProxyFactory(
new AttributeEnabledDAO<Article>());
articleDAOFactory.AddAdvisor(new DefaultPointcutAdvisor(
new AttributeMatchMethodPointcut(typeof(TransactionAttribute)),
new TransactionAroundAdvice()));
IDAO<Article> articleDao;
lock (synchObject){articleDao = (IDAO<Article>) articleDAOFactory.GetProxy();
}
Article article = articleDao.Load(1);article.Name = "Paracetamol";article.SalePrice = rnd.NextDouble();
articleDao.Save(article);
No comments:
Post a Comment