springboot2 + mybatis 多種方式實現多數據配置

  • 2020 年 3 月 29 日
  • 筆記

     業務系統複雜程度增加,為了解決數據庫I/O瓶頸,很自然會進行拆庫拆表分服務來應對。這就會出現一個系統中可能會訪問多處數據庫,需要配置多個數據源。

第一種場景:項目服務從其它多處數據庫取基礎數據進行業務處理,因此各庫之間不會出現重表等情況。

第二種場景:為了減輕寫入壓力進行讀寫分庫,讀走從庫,寫為主庫。此種表名等信息皆為一致。

第三種場景:以上兩種皆有。對於某些業務需要大數據量的匯總統計,希望不影響正常業務必須走從庫(表信息一致),某些配置信息不存在讀寫壓力,出現不分庫(表信息不一致)

 

項目源代碼:

https://github.com/zzsong/springboot-multiple-datasource.git

有三個目錄:

one:
直接使用多@Bean配置,@MapperScan來路徑區分讀何庫

two:
使用註解的方式來標識走何dataSource,AOP攔截注入動態數據源

third:
使用spring的Bean命名策略進行區分數據來源

項目技術選型: springBoot2.2.5 + mybatis + druid + mysql

先看主要的pom包

        <parent>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-parent</artifactId>          <version>2.2.5.RELEASE</version>          <relativePath/>      </parent>                    <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-data-jdbc</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-data-jdbc</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-aop</artifactId>          </dependency>          <dependency>              <groupId>org.mybatis.spring.boot</groupId>              <artifactId>mybatis-spring-boot-starter</artifactId>              <version>2.1.2</version>          </dependency>            <dependency>              <groupId>mysql</groupId>              <artifactId>mysql-connector-java</artifactId>              <version>8.0.19</version>          </dependency>          <dependency>              <groupId>com.alibaba</groupId>              <artifactId>druid-spring-boot-starter</artifactId>              <version>1.1.21</version>          </dependency>        

application.yml

spring:    datasource:      druid:        core:          url: jdbc:mysql:///kc_core?characterEncoding=utf-8&serverTimezone=Asia/Shanghai          username: root          password: 123456          driver-class-name: com.mysql.cj.jdbc.Driver          type: com.alibaba.druid.pool.DruidDataSource        schedule:          url: jdbc:mysql:///kc_schedule?characterEncoding=utf-8&serverTimezone=Asia/Shanghai          username: root          password: 123456          driver-class-name: com.mysql.cj.jdbc.Driver          type: com.alibaba.druid.pool.DruidDataSource

mysql新版本必須帶有serverTimezone,不然會報連接異常。

第一種:通過@MapperScans 掃描匹配相關的數據源

@Configuration  @MapperScans({          @MapperScan(basePackages = "com.zss.one.mapper.core", sqlSessionTemplateRef = "coreSqlSessionTemplate",sqlSessionFactoryRef = "coreSqlSessionFactory"),          @MapperScan(basePackages = "com.zss.one.mapper.schedule", sqlSessionTemplateRef = "scheduleSqlSessionTemplate",sqlSessionFactoryRef = "scheduleSqlSessionFactory")  })  public class MybatisOneConfig {        @Bean      @ConfigurationProperties(prefix = "spring.datasource.druid.core")      public DataSource coreDataSource(){          return DruidDataSourceBuilder.create().build();      }        @Bean      public SqlSessionFactory coreSqlSessionFactory(@Qualifier("coreDataSource") DataSource coreDataSource) throws Exception {          SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();          sessionFactory.setDataSource(coreDataSource);          sessionFactory.getObject().getConfiguration().setJdbcTypeForNull(null);          sessionFactory.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);          return sessionFactory.getObject();      }        @Bean      public SqlSessionTemplate coreSqlSessionTemplate(@Qualifier("coreSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {          return new SqlSessionTemplate(sqlSessionFactory);      }        //======schedule========      @Bean      @ConfigurationProperties(prefix = "spring.datasource.druid.schedule")      public DataSource scheduleDataSource(){          return DruidDataSourceBuilder.create().build();      }        @Bean      public SqlSessionFactory scheduleSqlSessionFactory(@Qualifier("scheduleDataSource") DataSource coreDataSource) throws Exception {          SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();          sessionFactory.setDataSource(coreDataSource);          sessionFactory.getObject().getConfiguration().setJdbcTypeForNull(null);          sessionFactory.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);          return sessionFactory.getObject();      }        @Bean      public SqlSessionTemplate scheduleSqlSessionTemplate(@Qualifier("scheduleSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {          return new SqlSessionTemplate(sqlSessionFactory);      }  }

第二種是動態數據源模式,通過AOP切入註解引導使用何數據源。用自定義註解@interface來標識方法走對應的數據源。

注意事項:類中的方法再調用帶數據源的方法,不能被AOP切入

@Target({ElementType.METHOD, ElementType.TYPE})  @Retention(RetentionPolicy.RUNTIME)  @Documented  public @interface TargetDataSource {      String value();  }

extends spring的動態DataSource路由來匹配

public class DynamicDataSource extends AbstractRoutingDataSource {        @Override      protected Object determineCurrentLookupKey() {          return  DataSourceContextRouting.getDataSourceName();      }  }

@Configuration  //@EnableConfigurationProperties(MybatisProperties.class)//不要使用此公共配置,Configuration會破壞相關dataSource的配置  @MapperScan("com.zss.two.mapper")  public class MybatisConfig {        @Bean      @ConfigurationProperties(prefix = "spring.datasource.druid.core")      public DataSource coreDataSource() {          return DruidDataSourceBuilder.create().build();      }        @Bean      @ConfigurationProperties(prefix = "spring.datasource.druid.schedule")      public DataSource scheduleDataSource() {          return DruidDataSourceBuilder.create().build();      }            @Autowired      @Qualifier("coreDataSource")      private DataSource coreDataSource;        @Autowired      @Qualifier("scheduleDataSource")      private DataSource scheduleDataSource;        @Bean      public DynamicDataSource dataSource() {          Map<Object, Object> targetDataSources = new HashMap<>();          targetDataSources.put(DataSourceConstants.CORE_DATA_SOURCE, coreDataSource);          targetDataSources.put(DataSourceConstants.SCHEDULE_DATA_SOURCE, scheduleDataSource);            DynamicDataSource dataSource = new DynamicDataSource();            //設置數據源映射          dataSource.setTargetDataSources(targetDataSources);  ////        設置默認數據源,當無法映射到數據源時會使用默認數據源          dataSource.setDefaultTargetDataSource(coreDataSource);          dataSource.afterPropertiesSet();          return dataSource;      }      /**       * 根據數據源創建SqlSessionFactory       */      @Bean      public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception {          SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();          sessionFactory.setDataSource(dataSource);          sessionFactory.getObject().getConfiguration().setJdbcTypeForNull(null);          sessionFactory.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);          return sessionFactory.getObject();      }        @Bean      public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {          return new SqlSessionTemplate(sqlSessionFactory);      }

 

第三種,自定義Bean命名策略,按beanName進行自動匹配使用數據源

@Component  public class CoreBeanNameGenerator implements BeanNameGenerator {      @Override      public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {          return "core"+ ClassUtils.getShortName(definition.getBeanClassName());      }  }      @Component  public class ScheduleBeanNameGenerator implements BeanNameGenerator {      @Override      public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {          return "schedule"+ ClassUtils.getShortName(definition.getBeanClassName());      }  }

使用mybatis MapperScannerConfigurer自動掃描,將Mapper接口生成注入到spring

    @Bean      public MapperScannerConfigurer coreMapperScannerConfig(CoreBeanNameGenerator coreBeanNameGenerator){          MapperScannerConfigurer configurer = new MapperScannerConfigurer();          configurer.setNameGenerator(coreBeanNameGenerator);          configurer.setBasePackage("com.zss.third.mapper.core,com.zss.third.mapper.order");          configurer.setSqlSessionFactoryBeanName("coreSqlSessionFactory");          configurer.setSqlSessionTemplateBeanName("coreSqlSessionTemplate");          return configurer;      }            @Bean      public MapperScannerConfigurer scheduleMapperScannerConfig(ScheduleBeanNameGenerator scheduleBeanNameGenerator){          MapperScannerConfigurer configurer = new MapperScannerConfigurer();          configurer.setNameGenerator(scheduleBeanNameGenerator);          configurer.setBasePackage("com.zss.third.mapper.schedule,com.zss.third.mapper.order");          configurer.setSqlSessionFactoryBeanName("scheduleSqlSessionFactory");          configurer.setSqlSessionTemplateBeanName("scheduleSqlSessionTemplate");          return configurer;      }

 

到此,三種多數據源匹配主要點介紹完,詳細直接下載github項目。 在resources/db含有相關測試表及數據腳本。