What is LCN mode

LCN mode is one of tX-LCN distributed transaction modes, including L-lock-lock transaction unit, C-confirm-confirm transaction module status, and notify-notify transaction unit

The principle of

LCN mode is through the way of Spring AOP proxy Connection to achieve the operation of local transactions, and then by TxManager unified coordination and control transactions. A sham operation will be performed when a local transaction commit is rolled back or a connection is closed, and the agent’s connection will be managed by the LCN connection pool.

Model features

  • This pattern is low embeddability to code.
  • This mode is limited to modules that have connection objects locally and can control transactions through connection objects.
  • In this mode, transaction submission and rollback are controlled by the local transaction party, which has a high guarantee for data consistency.
  • The drawback of this pattern is that the broker’s connections need to be released together with the transaction initiator, which increases the connection time.

The source code interpretation

Let’s start with the key classes DataSourceAspect- DataSourceAspect class, TransactionAspect TransactionAspect class, and LcnConnectionProxylcn Connection agent class, DTXLogicWeaver distributed transaction scheduler, DTXServiceExecutor Distributed transaction executor

The role of DataSourceAspect

  • The source code
@Aspect @Component public class DataSourceAspect implements Ordered { private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class); private final TxClientConfig txClientConfig; private final DTXResourceWeaver dtxResourceWeaver; public DataSourceAspect(TxClientConfig txClientConfig, DTXResourceWeaver dtxResourceWeaver) { this.txClientConfig = txClientConfig; this.dtxResourceWeaver = dtxResourceWeaver; } @Around("execution(* javax.sql.DataSource.getConnection(..) )") public Object around(ProceedingJoinPoint point) throws Throwable { return this.dtxResourceWeaver.getConnection(() ->  { return (Connection)point.proceed(); }); } public int getOrder() { return this.txClientConfig.getResourceOrder(); }}Copy the code

From the source of this class, we can know that LCN mode mainly interception proxy for database connections. Access to the database is brokered by LCN.

TransactionAspect role

  • The source code
@Aspect @Component public class TransactionAspect implements Ordered { private static final Logger log = LoggerFactory.getLogger(TransactionAspect.class); private final TxClientConfig txClientConfig; private final DTXLogicWeaver dtxLogicWeaver; public TransactionAspect(TxClientConfig txClientConfig, DTXLogicWeaver dtxLogicWeaver) { this.txClientConfig = txClientConfig; this.dtxLogicWeaver = dtxLogicWeaver; } @Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.LcnTransaction)") public void lcnTransactionPointcut() { } @Around("lcnTransactionPointcut() && ! txcTransactionPointcut()&& ! tccTransactionPointcut() && ! txTransactionPointcut()") public Object runWithLcnTransaction(ProceedingJoinPoint point) throws Throwable { DTXInfo dtxInfo = DTXInfo.getFromCache(point); LcnTransaction lcnTransaction = (LcnTransaction)dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class); dtxInfo.setTransactionType("lcn"); dtxInfo.setTransactionPropagation(lcnTransaction.propagation()); DTXLogicWeaver var10000 = this.dtxLogicWeaver; point.getClass(); return var10000.runTransaction(dtxInfo, point::proceed); } public int getOrder() { return this.txClientConfig.getDtxAspectOrder(); }}Copy the code

From the source code of this class, we can see how to do this by parsing the @lCNTransaction annotation. The code calls the DTXLogicWeaver class

DTXLogicWeaver role

public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable { if (Objects.isNull(DTXLocalContext.cur())) { DTXLocalContext.getOrNew(); log.debug("<---- TxLcn start ---->"); DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew(); TxContext txContext; if (this.globalContext.hasTxContext()) { txContext = this.globalContext.txContext(); dtxLocalContext.setInGroup(true); log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId()); } else { txContext = this.globalContext.startTx(); } if (Objects.nonNull(dtxLocalContext.getGroupId())) { dtxLocalContext.setDestroy(false); } dtxLocalContext.setUnitId(dtxInfo.getUnitId()); dtxLocalContext.setGroupId(txContext.getGroupId()); dtxLocalContext.setTransactionType(dtxInfo.getTransactionType()); 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()); boolean var15 = false; Object var6; try { var15 = true; var6 = this.transactionServiceExecutor.transactionRunning(info); var15 = false; } finally { if (var15) { if (dtxLocalContext.isDestroy()) { synchronized(txContext.getLock()) { txContext.getLock().notifyAll(); } if (! dtxLocalContext.isInGroup()) { this.globalContext.destroyTx(); } DTXLocalContext.makeNeverAppeared(); TracingContext.tracing().destroy(); } log.debug("<---- TxLcn end ---->"); } } if (dtxLocalContext.isDestroy()) { synchronized(txContext.getLock()) { txContext.getLock().notifyAll(); } if (! dtxLocalContext.isInGroup()) { this.globalContext.destroyTx(); } DTXLocalContext.makeNeverAppeared(); TracingContext.tracing().destroy(); } log.debug("<---- TxLcn end ---->"); return var6; } else { return business.call(); }}Copy the code

DTXServiceExecutor (DTXServiceExecutor, DTXServiceExecutor, DTXServiceExecutor

DTXServiceExecutor role

/** * Transaction service execution ** @param info info * @return Object * @throws Throwable Throwable */ public Object transactionRunning(TxTransactionInfo info) throws Throwable { // 1. String transactionType = info.getTransactionType(); / / 2. The acquisition transaction propagation state DTXPropagationState propagationState = propagationResolver. ResolvePropagationState (info); / / 2.1 if not participate in a distributed transaction is terminated if (propagationState. IsIgnored () {return info. GetBusinessCallback () call (); } / / 3. Get local distributed transaction controller DTXLocalControl DTXLocalControl = txLcnBeanHelper. LoadDTXLocalControl (transactionType, propagationState); 4.1 Record the transaction type to the transaction context Set<String> transactionTypeSet = globalContext.txContext(info.getGroupId()).getTransactionTypes(); transactionTypeSet.add(transactionType); dtxLocalControl.preBusinessCode(info); Txlogger.txtrace (info.getgroupid (), info.getUnitid (), "pre Business code, unit Type: {}", transactionType); / / 4.3 perform business Object result. = dtxLocalControl doBusinessCode (info); Txlogger.txtrace (info.getgroupid (), info.getunitid (), "Business Success "); dtxLocalControl.onBusinessCodeSuccess(info, result); return result; } catch (TransactionException e) { txLogger.error(info.getGroupId(), info.getUnitId(), "before business code error"); throw e; } catch (Throwable e) {// 4.5 Service execution fails TXLogger. error(info.getgroupid (), info.getunitid (), Transactions. "business code error"); dtxLocalControl.onBusinessCodeError(info, e); throw e; DtxLocalControl} finally {/ / 4.6 business has been completed. The postBusinessCode (info); }}Copy the code

As you can see from the above code, this class is the key class for the entire transaction execution.

The above is the core code of LCN mode, and other branch codes are not described

In actual combat

We planned two TX-lCN tX-LCN tX-LCN tX-LCN tCN-ORDER service and LCN-pay service respectively. Our idea is that the order service calls the payment service, and inserts insert data into the order service table T_ORDER and payment service table t_pay respectively.

Order service core code and data sheet scripts

  • code
/** * @author:triumphxx * @date :2021/10/24 * @time :2:13 PM * http://blog.triumphxx.com.cn * @GitHub https://github.com/triumphxx * @Desc: **/ @RestController public class LcnOrderController { @Autowired TOrderDao tOrderDao; @Autowired private RestTemplate restTemplate; @PostMapping("/add-order") @Transactional(rollbackFor = Exception.class) @LcnTransaction public String add(){ TOrder bean = new TOrder(); bean.setTId(1); bean.setTName("order"); restTemplate.postForEntity("http://lcn-pay/add-pay","",String.class); // int i = 1/0; tOrderDao.insert(bean); Return "Order added successfully "; }}Copy the code
  • The script
CREATE TABLE `t_order` (
   `t_id` int(11) NOT NULL,
   `t_name` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Copy the code

Payment service core code and data sheet scripts

  • code
/** * @author:triumphxx * @date :2021/10/24 * @time :2:26 PM * http://blog.triumphxx.com.cn * @GitHub https://github.com/triumphxx * @Desc: **/ @RestController public class LcnPayController { @Autowired TPayDao tPayDao; @PostMapping("/add-pay") @Transactional(rollbackFor = Exception.class) @LcnTransaction public String addPay(){ TPay tPay  = new TPay(); tPay.setTId(1); tPay.setTName("t_pay"); int i = tPayDao.insertSelective(tPay); Return "New payment succeeded "; }}Copy the code
  • The script
CREATE TABLE `t_pay` (
     `t_id` int(11) NOT NULL,
     `t_name` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Copy the code

The test process

  • Start theRedis
  • Start theTM
  • Start the registryeureka-server
  • Start the servicelcn-order
  • Start the servicelcn-pay
  • Request interfacehttp://localhost:8001/add-order
  • The code creates an exception to see if the data is rolled back

summary

In this paper, we analyzed the principle of TX-LCN distributed transaction LCN mode and related source code, as well as the testing of building services. I hope I can help you. Source code address source code portal