1. 程式人生 > 其它 >CF466C Number of Ways 字首和

CF466C Number of Ways 字首和

關於多資料來源解決方案

目前在SpringBoot框架基礎上多資料來源的解決方案大多手動建立多個DataSource,後續方案有三:

  1. 繼承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,使用AOP切面注入相應的資料來源 ,但是這種做法僅僅適用單Service方法使用一個數據源可行,如果單Service方法有多個數據源執行會造成誤讀。
  2. 通過DataSource配置 JdbcTemplateBean,直接使用 JdbcTemplate操控資料來源。
  3. 分別通過DataSource建立SqlSessionFactory
    並掃描相應的Mapper檔案和Mapper介面。

MybatisPlus多資料來源及事務解決思路

MybatisPlus的多資料來源

我通過閱讀原始碼,發現MybatisPlus的多資料來源解決方案正是AOP,繼承了org.springframework.jdbc.datasource.AbstractDataSource,有自己對ThreadLocal的處理。通過註解切換資料來源。也就是說,MybatisPlus只支援在單Service方法內操作一個數據源,畢竟官網都指明——“強烈建議只註解在service實現上”。

而後,注意看com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder

,也就是MybatisPlus是如何切換資料來源的。

重點看:

/**
 * 為什麼要用連結串列儲存(準確的是棧)
 * <pre>
 * 為了支援巢狀切換,如ABC三個service都是不同的資料來源
 * 其中A的某個業務要調B的方法,B的方法需要呼叫C的方法。一級一級呼叫切換,形成了鏈。
 * 傳統的只設置當前執行緒的方式不能滿足此業務需求,必須模擬棧,後進先出。
 * </pre>
 */
 private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
 @Override
 protected Object initialValue() {
  return new ArrayDeque();
 }
 };

這段話翻譯為大家都能懂得的意思就是“可以同時操控多個數據源”。那麼,在MYSQL中,有語法為schemaName+. +tableName,如此一來就不會誤走資料來源了。

我繼續看MybatisPlus是如何利用mybatis本身的ORM機制將實體類自動對映以及生成SQL語句的(這裡插一句,MybatisPlus的原始碼易讀懂,寫的很不錯)。無意看到了註解com.baomidou.mybatisplus.annotation.TableName中的schema,如果在類上加schema,在生成SQL語句時就會生成schemaName+. +tableName格式。

MybatisPlus多資料來源事務(JTA

簡單說明一下JTA

JTA包括事務管理器(Transaction Manager)和一個或多個支援 XA 協議的資源管理器 ( Resource Manager ) 兩部分, 可以將資源管理器看做任意型別的持久化資料儲存;事務管理器則承擔著所有事務參與單元的協調與控制。

JTA只是提供了一個介面,並沒有提供具體的實現。

不過Atomikos對其進行了實現,而後SpringBoot將其進行了整合,對其進行了託管,很方便開發者拿來即用。

其中事務管理器的主要部分為UserTransaction 介面,開發人員通過此介面在資訊系統中實現分散式事務;而資源管理器則用來規範提供商(如資料庫連線提供商)所提供的事務服務,它約定了事務的資源管理功能,使得 JTA 可以在異構事務資源之間執行協同溝通。

通常接入JTA步驟(目的就是讓JTAUserTransaction接管驅動為分散式的資料來源,通常為AtomikosDataSourceBean):

  • 配置好AtomikosDataSourceBean
  • AtomikosDataSourceBean交給SqlSessionFactory
  • 配置UserTransaction事務管理。

但是我們用的是MybatisPlus,我們需要做的是接管MybatisPlus每一個數據源的配置,然後再把資料來源依次交給MybatisPlus進行管理。

看看MybatisPlus是怎麼進行多資料來源配置的,原始碼裡有這幾個地方需要重點看一下:

  • com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider,這個就是MybatisPlus多資料來源配置的方式,利用HashMap來裝載。
  • com.baomidou.dynamic.datasource.DynamicDataSourceCreator,這個是每個資料來源的配置方式。

其中com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider實現了介面com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider,是該介面的預設的實現。也就是說我們只需要實現該介面,自己配置多資料來源以及每個資料來源的驅動,成為該介面的預設實現就OK。

實現該介面,配置多資料來源:

package xxx.xxx.xxx.config;

import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author : zuoyu
 * @description : 接管MybatisPlus多資料來源至Atomikos管理
 * @date : 2020-06-01 16:36
 **/
@Service
@Primary
public class DynamicDataSourceProviderImpl implements DynamicDataSourceProvider {


 /**
  * 配置檔案資料的鬆散繫結
  */
 private final DynamicDataSourceProperties properties;

 /**
  * Atomikos驅動資料來源建立
  */
 private final AtomikosDataSourceCreator atomikosDataSourceCreator;

 public DynamicDataSourceProviderImpl(DynamicDataSourceProperties properties,AtomikosDataSourceCreator atomikosDataSourceCreator) {
  this.properties = properties;
  this.atomikosDataSourceCreator = atomikosDataSourceCreator;
 }

 @Override
 public Map<String,DataSource> loadDataSources() {
  Map<String,DataSourceProperty> dataSourcePropertiesMap = properties.getDatasource();
  Map<String,DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
  for (Map.Entry<String,DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
   String pollName = item.getKey();
   DataSourceProperty dataSourceProperty = item.getValue();
   dataSourceProperty.setPollName(pollName);
   dataSourceMap.put(pollName,atomikosDataSourceCreator.createDataSource(dataSourceProperty));
  }
  return dataSourceMap;
 }
}

Atomikos驅動資料來源建立:

package xxx.xxx.xxx.config;

import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

/**
 * @author : zuoyu
 * @description : 事務資料來源
 * @date : 2020-06-01 17:30
 **/
@Component
public class AtomikosDataSourceCreator {
 /**
  * 建立資料來源
  *
  * @param dataSourceProperty 資料來源資訊
  * @return 資料來源
  */
 public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
  MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
  mysqlXaDataSource.setUrl(dataSourceProperty.getUrl());
  mysqlXaDataSource.setPassword(dataSourceProperty.getPassword());
  mysqlXaDataSource.setUser(dataSourceProperty.getUsername());
  AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
  xaDataSource.setXaDataSource(mysqlXaDataSource);
  xaDataSource.setMinPoolSize(5);
  xaDataSource.setBorrowConnectionTimeout(60);
  xaDataSource.setMaxPoolSize(20);
  xaDataSource.setXaDataSourceClassName(dataSourceProperty.getDriverClassName());
  xaDataSource.setTestQuery("SELECT 1 FROM DUAL");
  xaDataSource.setUniqueResourceName(dataSourceProperty.getPollName());
  return xaDataSource;
 }
}

配置JTA事務管理器:

package xxx.xxx.xxx.config;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

/**
 * @author : zuoyu
 * @description : 分散式事務配置
 * @date : 2020-06-01 17:55
 **/
@Configuration
@EnableTransactionManagement
public class TransactionManagerConfig {

 @Bean(name = "userTransaction")
 public UserTransaction userTransaction() throws Throwable {
  UserTransactionImp userTransactionImp = new UserTransactionImp();
  userTransactionImp.setTransactionTimeout(10000);
  return userTransactionImp;
 }

 @Bean(name = "atomikosTransactionManager")
 public TransactionManager atomikosTransactionManager() throws Throwable {
  UserTransactionManager userTransactionManager = new UserTransactionManager();
  userTransactionManager.setForceShutdown(false);
  return userTransactionManager;
 }

 @Bean(name = "transactionManager")
 @DependsOn({"userTransaction","atomikosTransactionManager"})
 public PlatformTransactionManager transactionManager() throws Throwable {
  return new JtaTransactionManager(userTransaction(),atomikosTransactionManager());
 }
}

如此,即可

這樣一來便可解決MybatisPlus多資料來源的誤走,且支援多資料來源下的事務問題。

做任何事情,重要的是思路,而不是搬磚。

到此這篇關於MybatisPlus多資料來源及事務解決思路的文章就介紹到這了,更多相關MybatisPlus多資料來源事務內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!