NC事务
1.NC中新建独立事务
NC中接口方法命名为method__RequiresNew(Object param) throws Exception ,后续步骤同新建NC组件一样——实现接口,在upm中注册接口。
public interface IGuanyiBillFilterService {
/**
* 更新已处理的单据记录
* @param records
*/
void updateBillRecord__RequiresNew(List<GuanYiErpBillRecord> records) throws DAOException;
}
代码中调用事务,需通过NCLocator进行远程组件调用事务才生效,直接调用不会新建事务。
IGuanyiBillFilterService service = NCLocator.getInstance().lookup(IGuanyiBillFilterService.class);
service.updateBillRecord__RequiresNew(records);
2.NC事务原理
当进行远程组件调用时会使用动态代理执行目标方法,其中会判断方法名是否具有_RequiresNew后缀来决定是否新建事务
public class CMTEJBServiceHandler
implements InvocationHandler
{
....
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
try {
if (method.getName().endsWith("_RequiresNew")) {
return this.cmtProxy.delegate_RequiresNew(this.wrapped, method, args);
}
return this.cmtProxy.delegate(this.wrapped, method, args);
}
catch (Throwable e) {
Throwable lastEJBException = getLastEJBException(e);
if (lastEJBException == null) {
throw e;
}
if (lastEJBException.getCause() != null) {
throw lastEJBException.getCause();
}
throw e;
}
}
....
}
下一步会进入到如下方法,其中需要关注的是beforeCallMethod和afterCallMethod,其中分别新建和结束事务
public class CMTProxy_Local
extends BeanBase
implements CMTProxyEjbObject
{
.....
public Object delegate_RequiresNew(Object arg0, Method arg1, Object[] arg2)
throws Exception
{
beforeCallMethod(200);
try {
o = _getBeanObject().delegate_RequiresNew(arg0, arg1, arg2);
}
catch (Exception e) {
er = e;
} catch (Throwable thr) {
er = new FrameworkEJBException("Fatal unknown error", thr);
}
try {
afterCallMethod(200, er);
}
.....
}
public Object delegate(Object arg0, Method arg1, Object[] arg2)
throws Exception
{
....
beforeCallMethod(201);
try {
o = _getBeanObject().delegate_RequiresNew(arg0, arg1, arg2);
}
catch (Exception e) {
er = e;
} catch (Throwable thr) {
er = new FrameworkEJBException("Fatal unknown error", thr);
}
try {
afterCallMethod(201, er);
}
......
}
}
让我们看看里面干了啥…
beforeCallMethod中首先判断组件是否是容器控制事务,获取事务类型和事务隔离级别后调用TransactionManager的begin方法
protected void beforeCallMethod(int methodId)
{
......
boolean isCmt = ((HomeBase)getEJBLocalHome()).getEJBBeanDescriptor().isCmt();
if (isCmt)
{
try
{
this.currentMethodTransectionType = getMethodTransectionType(methodId);
int isolateLevel = getMethodIsolateLevelType(methodId);
setIerpTransactionManagerProxy(TransactionFactory.getTMProxy());
getIerpTransactionManagerProxy().begin(this.currentMethodTransectionType, isolateLevel);
}
catch (Exception e) {
Logger.error("BeforeCallMethod", e);
}
}
else {
if (getIerpUserTransaction() == null) {
setIerpTransactionManagerProxy(null);
setIerpUserTransaction(TransactionFactory.getUTransaction());
}
getIerpUserTransaction().bindToCurrentThread();
}
......
}
UAPTransactionManager中的begin方法,其中3是对应声明了_RequiresNew的接口方法,1是对应普通接口方法,自己debug可以跟踪到。
- 使用_RequiresNew方法会直接新建一个事务
- 不使用_RequiresNew会判断堆栈中是否已有事务,有就直接使用栈顶事务,没有就新建事务
- 之后封装成context,压栈
public void begin(int transType) throws NotSupportedException, SystemException {
switch (transType) {
case 1:
if (this.tranStack.isEmpty()) {
createTransaction(TransactionContextType.SOURCE);
} else {
createTransaction(TransactionContextType.JOINED);
}
break;
case 3:
createTransaction(TransactionContextType.SOURCE);
break;
case 4:
if (this.tranStack.isEmpty()) {
throw new SystemException();
}
createTransaction(TransactionContextType.JOINED);
break;
case 5:
if (!this.tranStack.isEmpty()) {
throw new SystemException();
}
createTransaction(TransactionContextType.NULL);
break;
case 2:
if (!this.tranStack.isEmpty()) {
createTransaction(TransactionContextType.NULL);
} else {
createTransaction(TransactionContextType.JOINED);
}
break;
case 0:
createTransaction(TransactionContextType.NULL);
break;
case 11:
createTransaction(TransactionContextType.JOINED);
try {
setCurInvokeSavePoint();
} catch (SQLException e) {
throw new NotSupportedException("savePoint error!");
}
case 6: case 7: case 8:
case 9: case 10: default:
throw new NotSupportedException("trans type error!");
}
}
private UAPTransactionContext createTransaction(TransactionContextType transType) throws SystemException
{
UAPTransaction uapTran = null;
if (transType == TransactionContextType.SOURCE) {
uapTran = new UAPTransaction();
}
if (transType == TransactionContextType.JOINED) {
if (this.tranStack.isEmpty()) {
throw new SystemException("no source Transaction,can not join ");
}
uapTran = (UAPTransaction)getTranContext().getTransaction();
} else {
uapTran = new UAPTransaction();
}
UAPTransactionContext tranText = new UAPTransactionContext(uapTran);
tranText.setTransType(transType);
this.tranStack.push(tranText);
return tranText;
}
afterCallMethod同理
protected void afterCallMethod(int methodId, Exception exception)
throws java.rmi.RemoteException
{
......
boolean isCmt = ((HomeBase)getEJBLocalHome()).getEJBBeanDescriptor().isCmt();
if (isCmt) {
getIerpTransactionManagerProxy().end(exception);
setIerpTransactionManagerProxy(null);
}
else
{
getIerpUserTransaction().unbindCurrentThread();
}
......
}
UAPTransactionManager中的end方法,其中会判断目标方法是否抛出异常
public void end(Exception ex)
{
IUAPTransactionManager m_tranManager = (IUAPTransactionManager)tm_local.get();
try {
if (ex != null) {
if (m_tranManager.getTranContext().needRBPoint()) {
if (!((UAPTransaction)m_tranManager.getTranContext().getTransaction()).getRollbackOnly())
{
m_tranManager.rollBackToCurInvokePoint();
}
}
else {
m_tranManager.setCurTransRollBack();
}
}
m_tranManager.commit();
} catch (Exception e) {
log.error("", e);
}
}
假如抛出异常,最终会调用UAPTransaction的setRollbackOnly方法,值得注意的是这里的rollback仅仅是设置了一个回滚标志,没有真正回滚,之后会说这样会导致什么问题。
public void setRollbackOnly()
throws IllegalStateException, SystemException
{
Logger.error("uaptransaction rollback@!!!", new Exception());
this.m_bRollbackOnly = true;
this.m_status = 1;
}
最终会调用commit方法,这里的commit也不是单纯的提交,他会判断之前是否有设置回滚标志来进行统一回滚,整个事务调用到此完成。
public void commit()
throws SecurityException, SystemException, RollbackException, HeuristicMixedException, javax.transaction.HeuristicRollbackException
{
if (this.m_bRollbackOnly) {
rollback();
} else {
this.m_status = 8;
boolean hasException = false;
for (DBConnection conn : this.connecionMap.values()) {
try {
conn.realCommit();
try
{
conn.backPool();
} catch (SQLException e) {
DBLogger.error(e.getMessage(), e);
}
this.m_status = 3;
}
catch (Exception e1)
{
DBLogger.error(e1.getMessage() + ":" + conn.getDataSourceName(), e1);
hasException = true;
}
finally {
try {
conn.backPool();
} catch (SQLException e) {
DBLogger.error(e.getMessage(), e);
}
}
}
this.connecionMap.clear();
transEnd();
if (hasException) {
throw new SystemException("commit error,Please check log");
}
}
}
知道了以上事务的原理后,假如我们代码里面有这样一种场景,即便调用方法B的时候捕捉异常且不向外抛出,之后的数据库操作也会回滚。其中是因为调用方法B之后代理会将当前的事务设置一个回滚标志,当完成方法A之后,整个事务会根据回滚标志统一回滚。
methodA__RequiresNew{
ClassB cb = NCLocate.lookup(ClassB.class);
try{
cb.methodB();
}catch(e){
}
//insert to db
insertsomething();
}
同大多数框架一样,NC实现了JTA规范,拓展:
https://www.jianshu.com/p/3938e7172443
3.checkpoint
4.Synchronization
待续