天天看點

SpringBoot實作Oracle和MongoDB跨庫事務

作者:金色領花

最近在做一個項目,項目基于SpringBoot實作後端,其中用到了Oracle資料庫和MongoDB作為資料存儲。

其中有的功能要求同時對Oracle和MongoDB進行讀寫操作,特别是寫操作如果失敗了需要對Oracle和MongoDB進行復原,最初我們直接使用了SpringBoot提供的@Transactional(rollbackFor = Exception.class)注解進行事務控制,可系統上線後發現異常發生時并沒有復原MongoDB的寫操作,導緻Oracle和MongoDB之間出現了資料差異。

為了解決這個我們我們基于SpringBoo提供的平台事務管理器PlatformTransactionManager通過注解和AOP程式設計實作了一個跨資料庫的事務異常管理器。實作方式如下:

1.先定義一個事務注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MultiTransactional {

  String[] values() default {};
}           

2.使用AOP技術定一個事務處理的切面處理類

@Slf4j
@Aspect
@Component
public class MultiTransactionalAspect {
  @Around("@annotation(multiTransactional)")
  public Object around(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) throws Throwable {

    Stack<PlatformTransactionManager> transactionManagerStack = new Stack<>();
    Stack<TransactionStatus> transactionStatusStack = new Stack<>();

    try {
      if (!openTransaction(transactionManagerStack, transactionStatusStack, multiTransactional)) {
        return null;
      }

      Object ret = pjp.proceed();

      commit(transactionManagerStack, transactionStatusStack);

      return ret;
    } catch (Throwable e) {

      rollback(transactionManagerStack, transactionStatusStack);

      log.error("{}, class:{}, method:{}", e.getMessage(), pjp.getTarget().getClass().getSimpleName(), pjp.getSignature().getName(), e);

      throw e;
    }
  }

  private boolean openTransaction(Stack<PlatformTransactionManager> transactionManagerStack, Stack<TransactionStatus> transactionStatusStack,
      MultiTransactional multiTransactional) {

    String[] transactionMangerNames = multiTransactional.values();

    //判斷是否配置了事務管理器,如果指定則使用指定的,如果未指定,則啟動使用全部事務管理器
    if (ArrayUtils.isNotEmpty(multiTransactional.values())) {
      for (String beanName : transactionMangerNames) {
        PlatformTransactionManager transactionManager = (PlatformTransactionManager) SpringContextHolder
            .getBean(beanName);
        TransactionStatus transactionStatus = transactionManager
            .getTransaction(new DefaultTransactionDefinition());

        transactionStatusStack.push(transactionStatus);
        transactionManagerStack.push(transactionManager);
      }
    } else {
      Map<String, PlatformTransactionManager> transactionManagerMap = SpringContextHolder.getBeansOfType(PlatformTransactionManager.class);
      for (PlatformTransactionManager transactionManager : transactionManagerMap.values()) {
        TransactionStatus transactionStatus = transactionManager
            .getTransaction(new DefaultTransactionDefinition());

        transactionStatusStack.push(transactionStatus);
        transactionManagerStack.push(transactionManager);
      }
    }

    return true;
  }

  private void commit(Stack<PlatformTransactionManager> transactionManagerStack, Stack<TransactionStatus> transactionStatusStack) {
    while (!transactionManagerStack.isEmpty()) {
      transactionManagerStack.pop().commit(transactionStatusStack.pop());
    }
  }

  private void rollback(Stack<PlatformTransactionManager> transactionManagerStack, Stack<TransactionStatus> transactionStatusStack) {
    while (!transactionManagerStack.isEmpty()) {
      transactionManagerStack.pop().rollback(transactionStatusStack.pop());
    }
  }
}           

3.MongoDB資料庫事務配置(隻有MongoDB4.0以上叢集部署的情況下才支援事務)

@Configuration
public class MongoTransactionManagerConfiguration {

  @Bean
  @ConditionalOnProperty(name = "food.mongodb.transaction", havingValue = "true")
  public MongoTransactionManager mongoTransactionManager(MongoDatabaseFactory factory) {
    return new MongoTransactionManager(factory);
  }

}           

4.在SpringBoot中強制注冊一個事務管理器

/**
 * 強制添加一個預設的TransactionManager
 */
@Configuration(proxyBeanMethods = false)
public class TransactionManagerConfiguration {

  @Bean
  @Primary
  DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource,
      ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
    DataSourceTransactionManager transactionManager = createTransactionManager(environment, dataSource);
    transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
    return transactionManager;
  }

  private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
    return environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE)
        ? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource);
  }
}           

至此程式就支援跨Oracle和MongoDB的事務處理了,在需要跨庫事務處理的方法上加上@MultiTransactional注解即可。

例如:

@MultiTransactional
private void updateDataToDb(OracleDataInfo oracleDataInfo, MongoDBDataInfo MongoDBDataInfo) throws Exception {
  //操作Oracle資料庫
  this.oracleDataService.deal(oracleDataInfo);
  //操作MongoDB資料庫
  this.mongoDBDataService.update(MongoDBDataInfo);
}           

這種事務處理的方式通過改造可以支援所有平台的事務,包括關系型資料庫、MongoDB等等。