1. 程式人生 > >mybatis 多資料來源動態切換

mybatis 多資料來源動態切換

>筆者主要從事c#開發,近期因為專案需要,搭建了一套spring-cloud微服務框架,集成了eureka服務註冊中心、 gateway閘道器過濾、admin服務監控、auth授權體系驗證,集成了redis、swagger、jwt、mybatis多資料來源等各項功能。 具體搭建過程後續另寫播客介紹。具體結構如下: ![](https://img2020.cnblogs.com/blog/1056199/202011/1056199-20201121105816810-85639865.png) >在搭建過程整合mybatis的時候,考慮到單一資料來源無法滿足實際業務需要,故結合c#的開發經驗,進行多資料來源動態整合。 mybatis的多資料來源可以採用兩種方式進行,第一種是分包方式實現,這種方式靈活性不高,而且較為繁瑣,故不做過多介紹。 另一種方式是採用AOP的思想,進行註解動態切換,參考網上教程,核心思想是依靠 繼承AbstractRoutingDataSource, 重寫determineCurrentLookupKey()方法,在該方法中使用DatabaseContextHolder獲取當前執行緒的dataSource。 但是網上方法大都是首先定義好各個datasource,比如有三個資料來源,就需要實現定義好三個datasource,筆者感覺這種方法,在我 目前這套框架中不夠靈活,因為筆者採用的是微服務框架,考慮到各個服務都有可能使用不同的資料來源,而多資料來源動態切換 是放在公共方法中實現的,如果每有新的資料來源就要定義一個,對程式碼的侵入性太高,在c#中,選擇資料來源很容易,根據連線名 稱就可以切換過去, 如下所示: ``` ``` 能不能像c#這樣根據連線名稱就自動選擇呢,筆者的連線配置如下所示: ``` spring: application: name: csg-auth datasource: kbase: - driverClassName: com.kbase.jdbc.Driver jdbcUrl: jdbc:kbase://127.0.0.1 username: DBOWN password: jdbc: - driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://localhost:3306/nacos?serverTimezone=GMT%2B8&useUnicode=false&characterEncoding=utf8&useSSL=false username: root password: 123456 connName: nacos - driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://localhost:3306/tpi?serverTimezone=GMT%2B8&useUnicode=false&characterEncoding=utf8&useSSL=false username: root password: 123456 connName: tpi ``` 其中kbase不用理會,是我們公司自己的資料庫,jdbc是維護的連線集合,其中connName就是我們自定義的連線名稱, 根據connName就可以自動切換到對應資料來源。 > 筆者實現程式碼如下: ![](https://img2020.cnblogs.com/blog/1056199/202011/1056199-20201121111913575-1546678626.png) ### 第一步 >首先,編寫DynamicDataSource類整合AbstractRoutingDataSource,重寫determineCurrentLookupKey方法,該方法主要作用是選擇資料來源的key 程式碼如下: ``` /** * 動態資料來源 * */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceHolder.getDataSource(); } } ``` ### 第二步 >第二部編寫DataSourceHolder類,提供設定、獲取、情況資料來源的方法,如下所示: ``` public class DataSourceHolder { /** * 執行緒本地環境 */ private static final ThreadLocal dataSources = new ThreadLocal(); /** * 設定資料來源 */ public static void setDataSources(String connName) { dataSources.set(connName); } /** * 獲取資料來源 */ public static String getDataSource() { return dataSources.get(); } /** * 清楚資料來源 */ public static void clearDataSource() { dataSources.remove(); } } ``` ### 第三步 >第三步,編寫DataSourceConfig類,該類主要作用是讀取配置檔案中的資料來源連線集合,以及維護專案資料來源的Bean物件, 程式碼如下: ``` @Component @ConfigurationProperties("spring.datasource") public class DataSourceConfig { private List jdbc; public Map getDataSourceMap(){ Mapmap=new HashMap<>(); if (jdbc!=null&&jdbc.size()>0){ for (int i = 0; i < jdbc.size() ; i++) { DataSourceBuilder dataSourceBuilder=DataSourceBuilder.create(); dataSourceBuilder.driverClassName(jdbc.get(i).getDriverClassName()); dataSourceBuilder.password(jdbc.get(i).getPassword()); dataSourceBuilder.username(jdbc.get(i).getUsername()); dataSourceBuilder.url(jdbc.get(i).getJdbcUrl()); map.put(jdbc.get(i).getConnName(),dataSourceBuilder.build()); } } return map; } @Bean public DataSource csgDataSource(){ DynamicDataSource dynamicDataSource=new DynamicDataSource(); MapdataSourceMap=getDataSourceMap(); dynamicDataSource.setTargetDataSources(dataSourceMap); Object object= dataSourceMap.values().toArray()[0]; dynamicDataSource.setDefaultTargetDataSource(object); return dynamicDataSource; } public void setJdbc(List jdbc) { this.jdbc = jdbc; } public List getJdbc(){ return this.jdbc; } } ``` >其中,getDataSourceMap()方法,作用是根據配置的連線集合,生成AbstractRoutingDataSource所需要的resolvedDataSources。 而csgDataSource()方法,添加了@Bean註解,作用是讓mybatis的SqlSessionFactory,能夠使用咱們維護的資料來源。 ### 第四部 >編寫MyBatisConfig類,該類主要作用是 配置好mybatis的資料來源。 ``` @Configuration public class MyBatisConfig { @Autowired private DataSource csgDataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(csgDataSource); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/**/*.xml")); return sqlSessionFactoryBean.getObject(); } @Bean public PlatformTransactionManager platformTransactionManager(){ return new DataSourceTransactionManager(csgDataSource); } } ``` >可以看到,這裡選擇的是我們定義好的csgDataSource,其作用也是如此。 ### 第五步 >編寫TargetDataSource註解 ``` /** * 註解標籤 * 作用於 方法、介面、類、列舉、註解 * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE}) public @interface TargetDataSource { String connName(); } ``` >其中connName,就是我們需要使用的資料來源 ### 第六步 >編寫DataSourceExchange,改類為切面,作用於TargetDataSource註解,故使用TargetDataSource註解的時候, 會根據connName自動選擇資料來源。 ``` @Aspect @Component public class DataSourceExchange { @Before("@annotation(TargetDataSource)") public void before(JoinPoint joinPoint){ MethodSignature sign = (MethodSignature) joinPoint.getSignature(); Method method = sign.getMethod(); boolean isMethodAop= method.isAnnotationPresent(TargetDataSource.class); if (isMethodAop){ TargetDataSource datasource = method.getAnnotation(TargetDataSource.class); DataSourceHolder.setDataSources(datasource.connName()); }else { if (joinPoint.getTarget().getClass().isAnnotationPresent(TargetDataSource.class)){ TargetDataSource datasource = joinPoint.getTarget().getClass().getAnnotation(TargetDataSource.class); DataSourceHolder.setDataSources(datasource.connName()); } } } @After("@annotation(TargetDataSource)") public void after(){ DataSourceHolder.clearDataSource(); } } ``` >改切面作用於方法執行前後,負責選擇、取消資料來源。 ### 第七部 >開始驗證,使用方法如下: ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @TargetDataSource(connName = "nacos") public List getList() { List list= userMapper.selectUserList(); return list; } } ``` > 在service中,在需要進行資料庫操作的方法上,新增TargetDataSource註解,即可自動切換到所需要的資料來源。 至此,mybatis就可以動態切換資料來源了。 筆者從事java開發工作不多,改方法可能不是太好,也請各位看官