seata分布式事务源码分析

一.参考

https://github.com/apache/incubator-seata

https://github.com/apache/incubator-seata-samples

Apache Seata


二.架构

(一).架构

官网架构图如下.

核心原理:

(二).单测

测试用例依次按顺序启动账号,库存,订单,业务测试服务类.代码入口如下

org.apache.seata.account.DubboAccountServiceStarter#main

org.apache.seata.storage.DubboStorageServiceStarter#main

org.apache.seata.order.DubboOrderServiceStarter#main

org.apache.seata.business.DubboBusinessServiceTester#main.

三.客户端启动源码

启动org.apache.seata.business.DubboBusinessServiceTester#main后,调用堆栈如下图所示:

GlobalTransactionScanner类实现了InitializingBean接口.添加了seata的系统启动时执行GlobalTransactionScanner#afterPropertiesSet方法,进入GlobalTransactionScanner#initClient方法.

该方法依次初始化TM,RM.然后进入GlobalTransactionScanner#findBusinessBeanNamesNeededEnhancement创建带有@GlobalTransactional注解的seata的代理对象.

(一).TM初始化.

进入TMClient#init().单例创建TmNettyRemotingClient对象,进入TmNettyRemotingClient#init方法初始化TM.依次调用registerProcessor,父类init,initConnection()三个方法.

1.TmNettyRemotingClient#registerProcessor注册和TC之间通信消息的处理器.如下图,具体不同消息的处理后面分析.

2.AbstractNettyRemotingClient#init方法向TC发起连接请求,创建线程池轮询发送消息队列.对外要发送的消息存储在AbstractNettyRemotingClient#basketMap队列中.启动netty.

3.initConnection重新初始化连接.

(二).RM初始化.

进入RMClient#init.设置RM对应的DefaultResourceManager,DefaultRMHandler.进入RmNettyRemotingClient#init,逻辑和TmNettyRemotingClient类似.这里只看注册处理器的不同.进入RmNettyRemotingClient#registerProcessor注册处理器,如下图

(三).GlobalTransactionScanner#findBusinessBeanNamesNeededEnhancement

把带有GlobalTransactional注解的bean放入GlobalTransactionScanner#NEED_ENHANCE_BEAN_NAME_SET集合中.

(四).GlobalTransactionScanner#wrapIfNecessary

因为GlobalTransactionScanner类继承了AbstractAutoProxyCreator类.在AbstractAutoProxyCreator#postProcessAfterInitialization方法中回调GlobalTransactionScanner#wrapIfNecessary方法.进入DefaultInterfaceParser#parserInterfaceToProxy,依次处理GlobalTransactional和TwoPhaseBusinessAction的注解.然后把所有带有注解的代理类按照order字段排序后,加到invocationHandlerList的职责链中.在wrapIfNecessary方法中把,职责链的第一个节点赋值到GlobalTransactionScanner#interceptor对象中,然后调用AbstractAutoProxyCreator#wrapIfNecessary创建代理对象.代码如下图:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        // do checkers
        if (!doCheckers(bean, beanName)) {
            return bean;
        }

        try {
            synchronized (PROXYED_SET) {
                if (PROXYED_SET.contains(beanName)) {
                    return bean;
                }
                if (!NEED_ENHANCE_BEAN_NAME_SET.contains(beanName)) {
                    return bean;
                }
                interceptor = null;
                //取出所有带有GlobalTransactional和TwoPhaseBusinessAction注解的业务类
                ProxyInvocationHandler proxyInvocationHandler = DefaultInterfaceParser.get().parserInterfaceToProxy(bean, beanName);
                if (proxyInvocationHandler == null) {
                    return bean;
                }

                interceptor = new AdapterSpringSeataInterceptor(proxyInvocationHandler);

                LOGGER.info("Bean [{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.toString());
                if (!AopUtils.isAopProxy(bean)) {
                    //创建业务类的代理对象
                    bean = super.wrapIfNecessary(bean, beanName, cacheKey);
                } else {
                    AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
                    Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
                    int pos;
                    for (Advisor avr : advisor) {
                        // Find the position based on the advisor's order, and add to advisors by pos
                        pos = findAddSeataAdvisorPosition(advised, avr);
                        advised.addAdvisor(pos, avr);
                    }
                }
                PROXYED_SET.add(beanName);
                return bean;
            }
        } catch (Exception exx) {
            throw new RuntimeException(exx);
        }
    }

如上图,需要代理的方法放在GlobalTransactionalInterceptorParser#methodsToProxy集合中.如下代码,创建代理对象.

@Override
    public ProxyInvocationHandler parserInterfaceToProxy(Object target, String objectName) throws Exception {
        Class<?> serviceInterface = DefaultTargetClassParser.get().findTargetClass(target);
        Class<?>[] interfacesIfJdk = DefaultTargetClassParser.get().findInterfaces(target);

        //检查是否有GlobalTransactional注解
        if (existsAnnotation(serviceInterface) || existsAnnotation(interfacesIfJdk)) {
            //创建代理对象
            ProxyInvocationHandler proxyInvocationHandler = createProxyInvocationHandler();
            ConfigurationFactory.getInstance().addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (CachedConfigurationChangeListener) proxyInvocationHandler);
            return proxyInvocationHandler;
        }

        return null;
    }

    protected ProxyInvocationHandler createProxyInvocationHandler() {
        return new GlobalTransactionalInterceptorHandler(FailureHandlerHolder.getFailureHandler(), methodsToProxy);
    }

四.事务执行源码

(一).客户端执行

测试代码如下:

public static void main(String[] args) throws InterruptedException {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(DubboBusinessServiceTester.class);

        BusinessService businessService = annotationConfigApplicationContext.getBean(BusinessService.class);
        Thread thread = new Thread(() -> {
            businessService.purchase("U100001", "C00321", 2);});
            String outPutRes = "{\"res\": \"success\"}";
            try {
                businessService.purchase("U100001", "C00321", 2);
                if (isInE2ETest()) {
                    writeE2EResFile(outPutRes);
                }
            } catch (Exception e) {
                if ("random exception mock!".equals(e.getMessage()) && isInE2ETest()) {
                    writeE2EResFile(outPutRes);
                }
                throw new RuntimeException(e);
            }
        thread.start();

        //keep run
        Thread.currentThread().join();
    }

调用带有GlobalTransactional注解的方法此时,进入GlobalTransactionalInterceptorHandler#handleGlobalTransaction方法处理事务.代码如下,调用TransactionalTemplate#execute,这个事务处理的核心方法,用到的模板回调方法在handleGlobalTransaction中声明.

Object handleGlobalTransaction(final InvocationWrapper methodInvocation,
                                   final AspectTransactional aspectTransactional) throws Throwable {
        boolean succeed = true;
        try {
            //这个方法在下面
            return transactionalTemplate.execute(new TransactionalExecutor() {
                @Override
                public Object execute() throws Throwable {
                    return methodInvocation.proceed();
                }

                public String name() {
                    String name = aspectTransactional.getName();
                    if (!StringUtils.isNullOrEmpty(name)) {
                        return name;
                    }
                    return formatMethod(methodInvocation.getMethod());
                }

                //创建事务对象
                @Override
                public TransactionInfo getTransactionInfo() {
                    // reset the value of timeout
                    int timeout = aspectTransactional.getTimeoutMills();
                    if (timeout <= 0 || timeout == DEFAULT_GLOBAL_TRANSACTION_TIMEOUT) {
                        timeout = defaultGlobalTransactionTimeout;
                    }

                    TransactionInfo transactionInfo = new TransactionInfo();
                    transactionInfo.setTimeOut(timeout);
                    transactionInfo.setName(name());
                    transactionInfo.setPropagation(aspectTransactional.getPropagation());
                    transactionInfo.setLockRetryInterval(aspectTransactional.getLockRetryInterval());
                    transactionInfo.setLockRetryTimes(aspectTransactional.getLockRetryTimes());
                    transactionInfo.setLockStrategyMode(aspectTransactional.getLockStrategyMode());
                    Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
                    for (Class<?> rbRule : aspectTransactional.getRollbackFor()) {
                        rollbackRules.add(new RollbackRule(rbRule));
                    }
                    for (String rbRule : aspectTransactional.getRollbackForClassName()) {
                        rollbackRules.add(new RollbackRule(rbRule));
                    }
                    for (Class<?> rbRule : aspectTransactional.getNoRollbackFor()) {
                        rollbackRules.add(new NoRollbackRule(rbRule));
                    }
                    for (String rbRule : aspectTransactional.getNoRollbackForClassName()) {
                        rollbackRules.add(new NoRollbackRule(rbRule));
                    }
                    transactionInfo.setRollbackRules(rollbackRules);
                    return transactionInfo;
                }
            });
        } catch (TransactionalExecutor.ExecutionException e) {
            GlobalTransaction globalTransaction = e.getTransaction();

            // If Participant, just throw the exception to original.
            if (globalTransaction.getGlobalTransactionRole() == Participant) {
                throw e.getOriginalException();
            }

            TransactionalExecutor.Code code = e.getCode();
            Throwable cause = e.getCause();
            boolean timeout = isTimeoutException(cause);
            switch (code) {
                case RollbackDone:
                    if (timeout) {
                        throw cause;
                    } else {
                        throw e.getOriginalException();
                    }
                case BeginFailure:
                    succeed = false;
                    failureHandler.onBeginFailure(globalTransaction, cause);
                    throw cause;
                case CommitFailure:
                    succeed = false;
                    failureHandler.onCommitFailure(globalTransaction, cause);
                    throw cause;
                case RollbackFailure:
                    failureHandler.onRollbackFailure(globalTransaction, e.getOriginalException());
                    throw e.getOriginalException();
                case Rollbacking:
                    failureHandler.onRollbacking(globalTransaction, e.getOriginalException());
                    if (timeout) {
                        throw cause;
                    } else {
                        throw e.getOriginalException();
                    }
                default:
                    throw new ShouldNeverHappenException(String.format("Unknown TransactionalExecutor.Code: %s", code), e.getOriginalException());
            }
        } finally {
            if (ATOMIC_DEGRADE_CHECK.get()) {
                EVENT_BUS.post(new DegradeCheckEvent(succeed));
            }
        }
    }

public Object execute(TransactionalExecutor business) throws Throwable {
        // 1. Get transactionInfo
        TransactionInfo txInfo = business.getTransactionInfo();
        if (txInfo == null) {
            throw new ShouldNeverHappenException("transactionInfo does not exist");
        }
        // 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.
        GlobalTransaction tx = GlobalTransactionContext.getCurrent();

        // 1.2 Handle the transaction propagation.
        Propagation propagation = txInfo.getPropagation();
        SuspendedResourcesHolder suspendedResourcesHolder = null;
        try {
            switch (propagation) {
                case NOT_SUPPORTED:
                    // If transaction is existing, suspend it.
                    if (existingTransaction(tx)) {
                        suspendedResourcesHolder = tx.suspend(false);
                    }
                    // Execute without transaction and return.
                    return business.execute();
                case REQUIRES_NEW:
                    // If transaction is existing, suspend it, and then begin new transaction.
                    if (existingTransaction(tx)) {
                        suspendedResourcesHolder = tx.suspend(false);
                    }
                    tx = GlobalTransactionContext.createNew();
                    // Continue and execute with new transaction
                    break;
                case SUPPORTS:
                    // If transaction is not existing, execute without transaction.
                    if (notExistingTransaction(tx)) {
                        return business.execute();
                    }
                    // Continue and execute with new transaction
                    break;
                case REQUIRED:
                    // If current transaction is existing, execute with current transaction,else create
                    tx = GlobalTransactionContext.getCurrentOrCreate();
                    break;
                case NEVER:
                    // If transaction is existing, throw exception.
                    if (existingTransaction(tx)) {
                        throw new TransactionException(
                                String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"
                                        , tx.getXid()));
                    } else {
                        // Execute without transaction and return.
                        return business.execute();
                    }
                case MANDATORY:
                    // If transaction is not existing, throw exception.
                    if (notExistingTransaction(tx)) {
                        throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
                    }
                    // Continue and execute with current transaction.
                    break;
                default:
                    throw new TransactionException("Not Supported Propagation:" + propagation);
            }

            // set current tx config to holder
            GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);
            
            if (tx.getGlobalTransactionRole() == GlobalTransactionRole.Participant) {
                LOGGER.info("join into a existing global transaction,xid={}", tx.getXid());
            }

            try {
                // 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
                //    else do nothing. Of course, the hooks will still be triggered.
                //开始事务
                beginTransaction(txInfo, tx);

                Object rs;
                try {
                    //执行业务方法
                    // Do Your Business 
                    rs = business.execute();
                } catch (Throwable ex) {
                    // 3. The needed business exception to rollback.
                    //回滚事务
                    completeTransactionAfterThrowing(txInfo, tx, ex);
                    throw ex;
                }

                // 4. everything is fine, commit. 
                //提交事务
                commitTransaction(tx, txInfo);

                return rs;
            } finally {
                //5. clear
                resumeGlobalLockConfig(previousConfig);
                triggerAfterCompletion(tx);
                cleanUp(tx);
            }
        } finally {
            // If the transaction is suspended, resume it.
            if (suspendedResourcesHolder != null) {
                tx.resume(suspendedResourcesHolder);
            }
        }
    }

(二).服务端执行

服务端接收请求的入口为DefaultCoordinator#onRequest.

五.开始事务

(一).客户端开始

进入org.apache.seata.tm.api.TransactionalTemplate#beginTransaction方法,进入DefaultGlobalTransaction#begin(),调用,然后在RootContext上绑定xid.RootContext的RootContext#CONTEXT_HOLDER成员中记录xid.如图,是向TC发起一个开始事务的请求.

  public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
        throws TransactionException {
        GlobalBeginRequest request = new GlobalBeginRequest();
        request.setTransactionName(name);
        request.setTimeout(timeout);
        GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
        if (response.getResultCode() == ResultCode.Failed) {
            throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
        }
        return response.getXid();
    }

(二).TC服务端开始

如下图,创建GlobalSession.

 public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
        throws TransactionException {
       //创建GlobalSession,生产xid
        GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name, timeout);
        MDC.put(RootContext.MDC_KEY_XID, session.getXid());

        session.begin();

        // transaction start event
        MetricsPublisher.postSessionDoingEvent(session, false);

        return session.getXid();
    }

六.执行事务

(一).客户端执行

如下图.先保存dml的sql执行前的镜像,然后自动提交执行业务的update的sql,在afterImage中执行修改后的记录的字段的查询sql.在prepareUndoLog中根据before和after的image构造undoLog记录插入UndoLog表.

1.保存sql镜像.进入UpdateExecutor#beforeImage方法,如图就是拼出要修改记录的select的sql,备份到UndoLog表.

2.写入UndoLog表,代码如下.

  protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException {
        if (beforeImage.getRows().isEmpty() && afterImage.getRows().isEmpty()) {
            return;
        }
        if (SQLType.UPDATE == sqlRecognizer.getSQLType()) {
            if (beforeImage.getRows().size() != afterImage.getRows().size()) {
                throw new ShouldNeverHappenException("Before image size is not equaled to after image size, probably because you updated the primary keys.");
            }
        }
        ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();

        TableRecords lockKeyRecords = sqlRecognizer.getSQLType() == SQLType.DELETE ? beforeImage : afterImage;
        String lockKeys = buildLockKey(lockKeyRecords);
        if (null != lockKeys) {
            connectionProxy.appendLockKey(lockKeys);
            //设置undoLog表的各个字段
            SQLUndoLog sqlUndoLog = buildUndoItem(beforeImage, afterImage);
            connectionProxy.appendUndoLog(sqlUndoLog);
        }
    }

七.提交事务

(一).客户端发起提交

代码如下图,主要是在DefaultGlobalTransaction#commit中向TC发起提交请求.

private void commitTransaction(GlobalTransaction tx, TransactionInfo txInfo)
            throws TransactionalExecutor.ExecutionException, TransactionException {
        if (tx.getGlobalTransactionRole() != GlobalTransactionRole.Launcher) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Ignore commit: just involved in global transaction [{}]", tx.getXid());
            }
            return;
        }
        if (isTimeout(tx.getCreateTime(), txInfo)) {
            // business execution timeout
            Exception exx = new TmTransactionException(TransactionExceptionCode.TransactionTimeout,
                String.format("client detected transaction timeout before commit, so change to rollback, xid = %s", tx.getXid()));
            rollbackTransaction(tx, exx);
            return;
        }

        try {
            triggerBeforeCommit();
            //向TC发起提交请求
            tx.commit();
            GlobalStatus afterCommitStatus = tx.getLocalStatus();
            TransactionalExecutor.Code code = TransactionalExecutor.Code.Unknown;
            switch (afterCommitStatus) {
                case TimeoutRollbacking:
                    code = TransactionalExecutor.Code.Rollbacking;
                    break;
                case TimeoutRollbacked:
                    code = TransactionalExecutor.Code.RollbackDone;
                    break;
                case Finished:
                    code = TransactionalExecutor.Code.CommitFailure;
                    break;
                default:
            }
            Exception statusException = null;
            if (GlobalStatus.isTwoPhaseHeuristic(afterCommitStatus)) {
                statusException = new TmTransactionException(TransactionExceptionCode.CommitHeuristic,
                    String.format("Global transaction[%s] not found, may be rollbacked.", tx.getXid()));
            } else if (GlobalStatus.isOnePhaseTimeout(afterCommitStatus)) {
                statusException = new TmTransactionException(TransactionExceptionCode.TransactionTimeout,
                    String.format("Global transaction[%s] is timeout and will be rollback[TC].", tx.getXid()));
            }
            if (null != statusException) {
                throw new TransactionalExecutor.ExecutionException(tx, statusException, code);
            }
            triggerAfterCommit();
        } catch (TransactionException txe) {
            // 4.1 Failed to commit
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.CommitFailure);
        }
    }

(二).服务端提交

进入.DefaultCore#commit.如下图.

 public GlobalStatus commit(String xid) throws TransactionException {
        GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
        if (globalSession == null) {
            return GlobalStatus.Finished;
        }

        if (globalSession.isTimeout()) {
            LOGGER.info("TC detected timeout, xid = {}", globalSession.getXid());
            return GlobalStatus.TimeoutRollbacking;
        }

        // just lock changeStatus
         //判断是否可以提交
        boolean shouldCommit = SessionHolder.lockAndExecute(globalSession, () -> {
            boolean shouldCommitNow = false;
            if (globalSession.getStatus() == GlobalStatus.Begin) {
                // Highlight: Firstly, close the session, then no more branch can be registered.
                globalSession.close();
                //遍历所有的BranchSession分支事务,如果都可以提交,则全局事务可以提交.
                if (globalSession.canBeCommittedAsync()) {
                    globalSession.asyncCommit();
                    MetricsPublisher.postSessionDoneEvent(globalSession, GlobalStatus.Committed, false, false);
                } else {
                    globalSession.changeGlobalStatus(GlobalStatus.Committing);
                    shouldCommitNow = true;
                }
                //clean session after changing status successfully.
                globalSession.clean();
            }
            return shouldCommitNow;
        });

        if (shouldCommit) {
            //代码下面分析,提交全局事务
            boolean success = doGlobalCommit(globalSession, false);
            //If successful and all remaining branches can be committed asynchronously, do async commit.
            if (success && globalSession.hasBranch() && globalSession.canBeCommittedAsync()) {
                globalSession.asyncCommit();
                return GlobalStatus.Committed;
            } else {
                return globalSession.getStatus();
            }
        } else {
            return globalSession.getStatus() == GlobalStatus.AsyncCommitting ? GlobalStatus.Committed : globalSession.getStatus();
        }
    }

如果应该提交的话,进入DefaultCore#doGlobalCommit方法提交全局事务.

public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {
        boolean success = true;
        // start committing event
        MetricsPublisher.postSessionDoingEvent(globalSession, retrying);

        if (globalSession.isSaga()) {
            success = getCore(BranchType.SAGA).doGlobalCommit(globalSession, retrying);
        } else {
            //遍历所有的分支事务
            List<BranchSession> branchSessions = globalSession.getSortedBranches();
            Boolean result = SessionHelper.forEach(branchSessions, branchSession -> {
                // if not retrying, skip the canBeCommittedAsync branches
                if (!retrying && branchSession.canBeCommittedAsync()) {
                    return CONTINUE;
                }
                //
                BranchStatus currentStatus = branchSession.getStatus();
                if (currentStatus == BranchStatus.PhaseOne_Failed) {
                    SessionHelper.removeBranch(globalSession, branchSession, !retrying);
                    return CONTINUE;
                }
                // Only databases with read-only optimization, such as Oracle,
                // will report the RDONLY status during XA transactions.
                // At this point, the branch transaction can be ignored.
                if (currentStatus == BranchStatus.PhaseOne_RDONLY
                        && branchSession.getBranchType() == BranchType.XA) {
                    SessionHelper.removeBranch(globalSession, branchSession, !retrying);
                    return CONTINUE;
                }
                try {
                    //向每个分支事务发起BranchCommitRequest分支事务提交RPC请求.
                    BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession);
                    if (isXaerNotaTimeout(globalSession,branchStatus)) {
                        LOGGER.info("Commit branch XAER_NOTA retry timeout, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
                        branchStatus = BranchStatus.PhaseTwo_Committed;
                    }
                    //省略代码
                    xxxxx
        return success;
    }

(三).客户端处理分支事务提交

调用DataSourceManager#branchCommit把提交请求加入到队列AsyncWorker#commitQueue里面.在方法AsyncWorker#dealWithGroupedContexts中处理队列里面的提交请求.处理方法如下,主要是删除UndoLog表里面的记录.

private void dealWithGroupedContexts(String resourceId, List<Phase2Context> contexts) {
        if (StringUtils.isBlank(resourceId)) {
            //ConcurrentHashMap required notNull key
            LOGGER.warn("resourceId is empty and will skip.");
            return;
        }
        DataSourceProxy dataSourceProxy = dataSourceManager.get(resourceId);
        if (dataSourceProxy == null) {
            LOGGER.warn("failed to find resource for {} and requeue", resourceId);
            addAllToCommitQueue(contexts);
            return;
        }

        Connection conn = null;
        try {
            conn = dataSourceProxy.getPlainConnection();
            UndoLogManager undoLogManager = UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType());

            // split contexts into several lists, with each list contain no more element than limit size
            List<List<Phase2Context>> splitByLimit = Lists.partition(contexts, UNDOLOG_DELETE_LIMIT_SIZE);
            for (List<Phase2Context> partition : splitByLimit) {
                //删除UndoLog表里面的记录.如果事务不是自动提交,则获取本地连接,提交本地事务.
                deleteUndoLog(conn, undoLogManager, partition);
            }
        } catch (SQLException sqlExx) {
            addAllToCommitQueue(contexts);
            LOGGER.error("failed to get connection for async committing on {} and requeue", resourceId, sqlExx);
        } finally {
            IOUtil.close(conn);
        }
    }

八.回滚事务

(一).客户端回滚

进入TransactionalTemplate#rollbackTransaction,主要是调用DefaultTransactionManager#rollback向TC发起回滚请求.

(二).服务端回滚

八.挂起事务

调用DefaultGlobalTransaction#suspend()清除RootContext的绑定,返回挂起的xid.在调用DefaultGlobalTransaction#resume恢复事务时,把上面保存的xid在写入RootContext中.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值