spring 多数据源手动管理事务,最大程度保障数据一致性

模板代码如下:

  @Autowired
    private DataSourceTransactionManager transactionManager;

    public void insert(CardEntity card, String dataSourceCloud, String tableNo) {
        TransactionStatus statusInsert = null;
        TransactionStatus statusMobile = null;
        TransactionStatus statusTel = null;
        try {
            DataSourceKeyUtils.set(dataSourceCloud);
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            statusInsert = transactionManager.getTransaction(def);
            cardService.insert(card, tableNo, dataSourceCloud);

            //插入索引
            statusMobile = insertIndex(card, IndexType.mobile);
            statusTel = insertIndex(card, IndexType.tel);

            if (statusTel != null) {
                transactionManager.commit(statusTel);
            }
            if (statusMobile != null) {
                transactionManager.commit(statusMobile);
            }
            transactionManager.commit(statusInsert);
        } catch (Exception e) {
            if (statusTel != null) {
                transactionManager.rollback(statusTel);
            }
            if (statusMobile != null) {
                transactionManager.rollback(statusMobile);
            }
            if (statusInsert != null) {
                transactionManager.rollback(statusInsert);
            }
            throw e;
        }
    }
在创建事务对象TransactionStatus 的时候,先手动切换数据源。事务的提交和回滚要安装创建顺序的逆顺序来提交或回滚。

原因分析:当第一次创建TransactionStatus 的时候会调用doGetTransaction()方法,这个方法会获取当前线程绑定的ConnectionHolder,第一次是null,然后就创建一个事务对象并开始。

boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
      definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;

doBegin方法主要是设置一个连接

if (txObject.getConnectionHolder() == null ||
      txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
   Connection newCon = this.dataSource.getConnection();
   if (logger.isDebugEnabled()) {
      logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
   }
   txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
当第二创建TransactionStatus 时,由于当前已存在事务所以要创建新的事务,同时挂起当前事务。

if (isExistingTransaction(transaction)) {
   // Existing transaction found -> check propagation behavior to find out how to behave.
   return handleExistingTransaction(definition, transaction, debugEnabled);
}
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
   boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
   DefaultTransactionStatus status = newTransactionStatus(
         definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
   doBegin(transaction, definition);
   prepareSynchronization(status, definition);
   return status;
}
挂起当前事务,实际就是将当前事务对象绑定的连接设置为null,同时解绑当前线程资源。挂起的事务会保存,

等当前事务提交之后会恢复之前的事务状态。所以要逆序提交或回滚

挂起当前事务:
@Override
protected Object doSuspend(Object transaction) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   txObject.setConnectionHolder(null);
   return TransactionSynchronizationManager.unbindResource(this.dataSource);
}
提交之后恢复之前的事务:

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
   status.setCompleted();
   if (status.isNewSynchronization()) {
      TransactionSynchronizationManager.clear();
   }
   if (status.isNewTransaction()) {
      doCleanupAfterCompletion(status.getTransaction());
   }
   if (status.getSuspendedResources() != null) {
      if (status.isDebug()) {
         logger.debug("Resuming suspended transaction after completion of inner transaction");
      }
      resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());
   }
}

后续:今天测试了一下有锁的情况,发现会堵塞,因为事务没提交,导致其他事务不能进行。原因在于就是同一个库我也开了多个事务。

改进方法:所有属于一个库的操作,使用同一个事务。

适用范围:这些sql操作没有先后关系。

关键代码如下:

private void sqlRun(Map<String, List<Runnable>> map) {
    List<TransactionStatus> transactionStatusList = new ArrayList<>();
    try {
        for (Map.Entry<String, List<Runnable>> entry : map.entrySet()) {
            DataSourceKeyUtils.set(entry.getKey());
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            TransactionStatus transaction = transactionManager.getTransaction(def);
            transactionStatusList.add(transaction);
            List<Runnable> runnableList = entry.getValue();
            for (Runnable runnable : runnableList) {
                runnable.run();
            }
        }
        Collections.reverse(transactionStatusList);
        for (TransactionStatus status : transactionStatusList) {
            transactionManager.commit(status);
        }
    } catch (Exception e) {
        Collections.reverse(transactionStatusList);
        for (TransactionStatus status : transactionStatusList) {
            transactionManager.rollback(status);
        }
        throw e;
    }
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值