一、代理Connect的獲取
TX-LCN中的LCN模式是通過代理數據庫連接,進而對事務進行控制的。通過靜態代理的方式包裝的原本來的Connection,並設置爲手動提交,根據事務狀態控制提交與回滾。
代理連接代碼如下,state爲事務狀態,state=1表示分佈式事務成功,本地連接提交,其它爲失敗進行本地連接回滾。
package com.codingapi.txlcn.tc.core.transaction.lcn.resource;
import com.codingapi.txlcn.txmsg.dto.RpcResponseState;
import lombok.extern.slf4j.Slf4j;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* @author CoderTnT
*/
@Slf4j
public class LcnConnectionProxy implements Connection {
private Connection connection;
public LcnConnectionProxy(Connection connection) {
this.connection = connection;
}
/**
* notify connection
*
* @param state transactionState
* @return RpcResponseState RpcResponseState
*/
public RpcResponseState notify(int state) {
try {
if (state == 1) {
log.debug("commit transaction type[lcn] proxy connection:{}.", this);
connection.commit();
} else {
log.debug("rollback transaction type[lcn] proxy connection:{}.", this);
connection.rollback();
}
connection.close();
log.debug("transaction type[lcn] proxy connection:{} closed.", this);
return RpcResponseState.success;
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
return RpcResponseState.fail;
}
}
// autoCommit設置false,表示 連接關閉自動提交,採用手動提交模式
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
connection.setAutoCommit(false);
}
@Override
public void commit() throws SQLException {
connection.commit();
}
@Override
public void rollback() throws SQLException {
connection.rollback();
}
@Override
public void close() throws SQLException {
connection.close();
}
@Override
public boolean getAutoCommit() throws SQLException {
return connection.getAutoCommit();
}
/*
* 忽略其他無關代碼。。。
*/
}
LcnConnectionProxy代理連接的獲取是通過AOP實現的
@Aspect
@Component
@Slf4j
public class DataSourceAspect implements Ordered {
private final TxClientConfig txClientConfig;
private final DTXResourceWeaver dtxResourceWeaver;
public DataSourceAspect(TxClientConfig txClientConfig, DTXResourceWeaver dtxResourceWeaver) {
this.txClientConfig = txClientConfig;
this.dtxResourceWeaver = dtxResourceWeaver;
}
//環繞通知獲取代理connection連接
@Around("execution(* javax.sql.DataSource.getConnection(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
return dtxResourceWeaver.getConnection(() -> (Connection) point.proceed());
}
@Override
public int getOrder() {
return txClientConfig.getResourceOrder();
}
}
@Component
@Slf4j
public class DTXResourceWeaver {
private final TxLcnBeanHelper txLcnBeanHelper;
public DTXResourceWeaver(TxLcnBeanHelper txLcnBeanHelper) {
this.txLcnBeanHelper = txLcnBeanHelper;
}
public Object getConnection(ConnectionCallback connectionCallback) throws Throwable {
//獲取事務本地上下文 dtxLocalContext
DTXLocalContext dtxLocalContext = DTXLocalContext.cur();
if (Objects.nonNull(dtxLocalContext) && dtxLocalContext.isProxy()) {
//獲取事務本地上下文的事務類型 dtxLocalContext的事務類型
String transactionType = dtxLocalContext.getTransactionType();
//根據事務類型獲取事務代理器:LcnTransactionResourceProxy或TccTransactionResourceProxy或TxcTransactionResourceProxy
TransactionResourceProxy resourceProxy = txLcnBeanHelper.loadTransactionResourceProxy(transactionType);
//由事務代理器根據connectionCallback Connection提供者 構造生成代理連接connection對象
Connection connection = resourceProxy.proxyConnection(connectionCallback);
log.debug("proxy a sql connection: {}.", connection);
return connection;
}
return connectionCallback.call();
}
}
二、整體正常執行的代碼流程
實現分佈式事務需要在特定執行的service方法上增加一個註解@LcnTransaction,這也是一個入口,整個流程的開始就在這裏。
TransactionAspect類是一個切面類,對@LcnTransaction註解設置了環繞通知,在碰到註解@LcnTransaction開始了整個流程。
2.1 分佈式事務執行方法的環繞
@Aspect //切面類註解
@Component
@Slf4j
public class TransactionAspect implements Ordered {
private final TxClientConfig txClientConfig;
private final DTXLogicWeaver dtxLogicWeaver;
public TransactionAspect(TxClientConfig txClientConfig, DTXLogicWeaver dtxLogicWeaver) {
this.txClientConfig = txClientConfig;
this.dtxLogicWeaver = dtxLogicWeaver;
}
/**
* DTC Aspect Common type() can lcn,tcc,txc and custom DTX type
*/
@Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.TxTransaction)")
public void txTransactionPointcut() {
}
/**
* DTC Aspect (Type of LCN)
*/
@Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.LcnTransaction)")
public void lcnTransactionPointcut() {
}
/**
* DTC Aspect (Type of TXC)
*/
@Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.TxcTransaction)")
public void txcTransactionPointcut() {
}
/**
* DTC Aspect (Type of TCC)
*/
@Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.TccTransaction)")
public void tccTransactionPointcut() {
}
@Around("txTransactionPointcut()")
public Object transactionRunning(ProceedingJoinPoint point) throws Throwable {
//見2.1.1
DTXInfo dtxInfo = DTXInfo.getFromCache(point);
TxTransaction txTransaction = dtxInfo.getBusinessMethod().getAnnotation(TxTransaction.class);
dtxInfo.setTransactionType(txTransaction.type());
dtxInfo.setTransactionPropagation(txTransaction.propagation());
return dtxLogicWeaver.runTransaction(dtxInfo, point::proceed);
}
@Around("lcnTransactionPointcut() && !txcTransactionPointcut()" +
"&& !tccTransactionPointcut() && !txTransactionPointcut()")
public Object runWithLcnTransaction(ProceedingJoinPoint point) throws Throwable {
//根據ProceedingJoinPoint 從緩存中獲取事務信息dtxInfo
DTXInfo dtxInfo = DTXInfo.getFromCache(point);
//獲取註解LcnTransaction,因爲註解LcnTransaction上配置不同的傳播行爲propagation,如REQUIRED,SUPPORTS等
LcnTransaction lcnTransaction = dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class);
//設置dtxInfo的事務類型爲LCN
dtxInfo.setTransactionType(Transactions.LCN);
//設置事務傳播行爲,發起方是REQUIRED,參與方式是SUPPORTS
dtxInfo.setTransactionPropagation(lcnTransaction.propagation());
//織入後 開始執行事務
//見2.1.2
return dtxLogicWeaver.runTransaction(dtxInfo, point::proceed);
}
/*
* 忽略其他代碼。。。
*/
}
2.1.1 在註解的開始從緩存中獲取了事務信息dtxInfo
public static DTXInfo getFromCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 根據切入點 獲取方法簽名信息signature
String signature = proceedingJoinPoint.getSignature().toString();
//根據方法簽名生成事務單元ID
String unitId = Transactions.unitId(signature);
//根據事務單元ID獲取事務信息dtxInfo
DTXInfo dtxInfo = dtxInfoCache.get(unitId);
if (Objects.isNull(dtxInfo)) {
// 根據切入點 獲取方法簽名信息methodSignature
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
//獲取要執行的方法,這是接口的方法並不是我們要的方法
Method method = methodSignature.getMethod();
//獲取目標類
Class<?> targetClass = proceedingJoinPoint.getTarget().getClass();
//獲取真實的實現方法
Method thisMethod = targetClass.getMethod(method.getName(), method.getParameterTypes());
//構造dtxInfo 對象
dtxInfo = new DTXInfo(thisMethod, proceedingJoinPoint.getArgs(), targetClass);
//緩存dtxInfo 對象 鍵:事務單元ID,值:dtxInfo
dtxInfoCache.put(unitId, dtxInfo);
}
dtxInfo.reanalyseMethodArgs(proceedingJoinPoint.getArgs());
return dtxInfo;
}
2.1.2 事務執行runTransaction方法
public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable {
// 1、獲取或新建一個線程變量DTXLocalContext
if (Objects.isNull(DTXLocalContext.cur())) {
DTXLocalContext.getOrNew();
} else {
return business.call();
}
log.debug("<---- TxLcn start ---->");
DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew();
TxContext txContext;
// ---------- 保證每個模塊在一個DTX下只會有一個TxContext ---------- //
//2、如果已經存在事務上下文則獲取,否則構建事務上下文TxContext
if (globalContext.hasTxContext()) {
// 有全局TC事務上下文的獲取上下文
txContext = globalContext.txContext();
dtxLocalContext.setInGroup(true);
log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
} else {
// 沒有的開啓本地事務上下文
// 見2.1.4
txContext = globalContext.startTx();
}
// 本地事務調用
if (Objects.nonNull(dtxLocalContext.getGroupId())) {
dtxLocalContext.setDestroy(false);
}
//3、爲DTXLocalContext設置參數
dtxLocalContext.setUnitId(dtxInfo.getUnitId());
dtxLocalContext.setGroupId(txContext.getGroupId());
dtxLocalContext.setTransactionType(dtxInfo.getTransactionType());
// 事務參數
//4、構造TxTransactionInfo 並設置參數
TxTransactionInfo info = new TxTransactionInfo();
info.setBusinessCallback(business);
info.setGroupId(txContext.getGroupId());
info.setUnitId(dtxInfo.getUnitId());
info.setPointMethod(dtxInfo.getBusinessMethod());
info.setPropagation(dtxInfo.getTransactionPropagation());
info.setTransactionInfo(dtxInfo.getTransactionInfo());
info.setTransactionType(dtxInfo.getTransactionType());
info.setTransactionStart(txContext.isDtxStart());
//LCN事務處理器
try {
//執行5、執行事務 事務業務執行
return transactionServiceExecutor.transactionRunning(info);
}
//6、finally做一些通知與銷燬操作。
//見2.1.5
finally {
// 線程執行業務完畢清理本地數據
if (dtxLocalContext.isDestroy()) {
// 通知事務執行完畢
synchronized (txContext.getLock()) {
txContext.getLock().notifyAll();
}
// TxContext生命週期是和事務組一樣(不與具體模塊相關的)
if (!dtxLocalContext.isInGroup()) {
globalContext.destroyTx();
}
DTXLocalContext.makeNeverAppeared();
TracingContext.tracing().destroy();
}
log.debug("<---- TxLcn end ---->");
}
}
2.1.3 主要說下2.1.2代碼註釋中的第2點什麼情況下要獲取已有的TxContext ,什麼時候構造新的
如果一個調用鏈是這樣的A->B->C 三個都是不同的模塊則會爲每個模塊都構造新的TxContext,保證一個模塊一個。
如果三個是兩個模塊,如A是一個模塊,B和C是一個模塊根據調用鏈的情況肯定是C的用B的TxContext。
2.1.4 構建新的TxContext
public TxContext startTx() {
TxContext txContext = new TxContext();
// 事務發起方判斷
txContext.setDtxStart(!TracingContext.tracing().hasGroup());
// 是否爲事務發起方
if (txContext.isDtxStart()) {
//是的話開始 初始化事務組操作,主要是構造事務組groupId,並放入以groupId和appMap爲鍵的Map 到tracingContext對象緩存中
TracingContext.tracing().beginTransactionGroup();
}
//設置txContext事務組groupId
txContext.setGroupId(TracingContext.tracing().groupId());
String txContextKey = txContext.getGroupId() + ".dtx";
//key與txContext緩存起來
attachmentCache.attach(txContextKey, txContext);
log.debug("Start TxContext[{}]", txContext.getGroupId());
return txContext;
}
2.1.5