用友NC事務原理

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

待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章