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
待續