最近在做一個項目,項目基于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等等。