【分享】微服务合并之多数据源整合(Dynamic Datasource)和Flyway对多数据源进行数据库脚本管理

项目背景:

在工作中,我们使用Flyway对数据库进行版本管理,每个微服务都有各自的数据库。最近我们需要对三个微服务合并成一个服务,且保留数据库不合并,有以下改动要求:

  1. 在不影响功能正常使用的情况下对代码结构尽量改动小
  2. 尽可能短的开发周期
  3. “分久必合,合久必分”,方便后面对服务的拆分

改动思路

  • 三个工程包合并成一个大包,可以通过具体的子包名区分以前的三个服务
  • Flyway脚本分开管理,单独作用在三个文件夹对应以前三个工程
  • 引入多数据源(Dynamic Datasource),根据包名和文件名对应以前三个工程独立数据源

现有三个微服务Master、DB1和DB2,整合成一个demo工程示例,三个微服务通过包区分

在这里插入图片描述
代码分包如下:
在这里插入图片描述


依赖配置:

这里只展示部分主要的多数据源依赖和Flyway的依赖,详细的配置可查看文末下载demo资源。

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- 多数据源依赖包 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!-- flyway依赖包 -->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            <version>5.2.1</version>
        </dependency>

多数据源配置方案:

application.yml配置多数据源,spring.dynamic.datasource下对数据源进行命名配置连接信息

spring:
  datasource:
    dynamic:
      primary: master #主数据源
      datasource:
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/master?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: 123456
          type: com.zaxxer.hikari.HikariDataSource
          hikari:
            minimumIdle: 10
            maximumPoolSize: 50
        db1:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/db1?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: 123456
          type: com.zaxxer.hikari.HikariDataSource
          hikari:
            minimumIdle: 10
            maximumPoolSize: 50
        db2:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/db2?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: 123456
          type: com.zaxxer.hikari.HikariDataSource
          hikari:
            minimumIdle: 10
            maximumPoolSize: 50

对于三个工程来说,每个工程自己的数据源配置是独立的,自己包和xml也是独立的,互不影响。

注入HikariDatasource属性工具类

DataSourceConfigUtil


import com.zaxxer.hikari.HikariConfig;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.env.Environment;

public class DataSourceConfigUtil {
    
    

    public static HikariConfig setDataSourceEnvConfig(String prefix1, String prefix, Environment env) {
    
    
        HikariConfig config = new HikariConfig();
        String driver = env.getProperty(prefix1 + "." + "driver-class-name");
        String dataSourceUrl = env.getProperty(prefix1 + "." + "url");
        String user = env.getProperty(prefix1 + "." + "username");
        String password = env.getProperty(prefix1 + "." + "password");

        String minimumIdle = env.getProperty(prefix + "." + "minimumIdle");
        String maximumPoolSize = env.getProperty(prefix + "." + "maximumPoolSize");
        String autoCommit = env.getProperty(prefix + "." + "autoCommit");
        String idleTimeout = env.getProperty(prefix + "." + "idleTimeout");
        String poolName = env.getProperty(prefix + "." + "poolName");
        String maxLifetime = env.getProperty(prefix + "." + "maxLifetime");
        String connectionTimeout = env.getProperty(prefix + "." + "connectionTimeout");
        String dataSourceClassName = env.getProperty(prefix + "." + "type");
        if (StringUtils.isNotBlank(dataSourceUrl)) {
    
    
            config.setJdbcUrl(dataSourceUrl);
        }
        if (StringUtils.isNotBlank(user)) {
    
    
            config.setUsername(user);
        }
        if (StringUtils.isNotBlank(password)) {
    
    
            config.setPassword(password);
        }
        if (StringUtils.isNotBlank(driver)) {
    
    
            config.setDriverClassName(driver);
        }

        if (StringUtils.isNotBlank(minimumIdle)) {
    
    
            config.setMinimumIdle(Integer.parseInt(minimumIdle));
        }
        if (StringUtils.isNotBlank(maximumPoolSize)) {
    
    
            config.setMaximumPoolSize(Integer.parseInt(maximumPoolSize));
        }
        if (StringUtils.isNotBlank(autoCommit)) {
    
    
            config.setAutoCommit(Boolean.parseBoolean(autoCommit));
        }
        if (StringUtils.isNotBlank(idleTimeout)) {
    
    
            config.setIdleTimeout(Integer.parseInt(idleTimeout));
        }
        if (StringUtils.isNotBlank(poolName)) {
    
    
            config.setPoolName(poolName);
        }
        if (StringUtils.isNotBlank(maxLifetime)) {
    
    
            config.setMaxLifetime(Integer.parseInt(maxLifetime));
        }
        if (StringUtils.isNotBlank(connectionTimeout)) {
    
    
            config.setConnectionTimeout(Integer.parseInt(connectionTimeout));
        }
        return config;
    }
}

Master工程的数据源配置:

MasterDatasourceConfig配置

@Component
@MapperScan(basePackages = "com.example.flywaydemo.master.dao",sqlSessionFactoryRef = "db1SqlSessionFactory")
public class MasterDatasourceConfig {
    
    
    private static final String MAPPER_LOCATION = "classpath:/mybatis/mapper/master/*Mapper.xml";

    @Bean("master")
//    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
    public DataSource getMasterDataSource(Environment env){
    
    
        HikariConfig hikariConfig = DataSourceConfigUtil.setDataSourceEnvConfig("spring.datasource.dynamic.datasource.master", "spring.datasource.dynamic.datasource.master.hikari", env);
        return new HikariDataSource(hikariConfig);
    }

    @Bean("masterSqlSessionFactory")
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("master") DataSource dataSource) throws Exception {
    
    
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        return bean.getObject();
    }
    @Bean("masterSqlSessionTemplate")
    public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
    
    
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

DB1工程的数据源配置:

Db1DatasourceConfig配置

@Configuration
@MapperScan(basePackages = "com.example.flywaydemo.db1.dao",sqlSessionFactoryRef = "db1SqlSessionFactory")
public class Db1DatasourceConfig  {
    
    
    private static final String MAPPER_LOCATION = "classpath:/mybatis/mapper/db1/*.xml";

    @Bean("db1")
//    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.db1")
    public DataSource getDb1DataSource(Environment env){
    
    
        HikariConfig hikariConfig = DataSourceConfigUtil.setDataSourceEnvConfig("spring.datasource.dynamic.datasource.db1", "spring.datasource.dynamic.datasource.db1.hikari", env);
        return new HikariDataSource(hikariConfig);
    }

    @Bean("db1SqlSessionFactory")
    public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1") DataSource dataSource) throws Exception {
    
    
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        return bean.getObject();
    }
    @Bean("db1SqlSessionTemplate")
    public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
    
    
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

DB2工程的数据源配置:

Db2DatasourceConfig配置

@Configuration
@MapperScan(basePackages = "com.example.flywaydemo.db2.dao", sqlSessionFactoryRef = "db2SqlSessionFactory")
public class Db2DatasourceConfig {
    
    
    private static final String MAPPER_LOCATION = "classpath:/mybatis/mapper/db2/*.xml";

    @Bean("db2")
//    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.db2")
    public DataSource getDb2DataSource(Environment env) {
    
    
        HikariConfig hikariConfig = DataSourceConfigUtil.setDataSourceEnvConfig("spring.datasource.dynamic.datasource.db2", "spring.datasource.dynamic.datasource.db2.hikari", env);
        return new HikariDataSource(hikariConfig);
    }

    @Bean("db2SqlSessionFactory")
    public SqlSessionFactory db2SqlSessionFactory(@Qualifier("db2") DataSource dataSource) throws Exception {
    
    
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        return bean.getObject();
    }

    @Bean("db2SqlSessionTemplate")
    public SqlSessionTemplate db2SqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
    
    
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}


Flyway配置

application.yml关闭自启动校验,flyway.enabled=false
这里我们通过java代码flyway.migrate()进行脚本迁移,延后通过spring IOC注入的时候执行该步骤

  flyway:
    enabled: false
    baseline-on-migrate: true
    locations: /db/migration

FlywayConfig配置,这里通过构造器注入所有数据源dataSources
通过key => dbName, value => locations 循环执行脚本迁移检查

@Component
public class FlywayConfig {
    
    

    private final Map<String, DataSource> dataSources;

    @Value("${spring.flyway.locations}")
    private String SQL_LOCATION;

    @Value("${spring.flyway.baseline-on-migrate}")
    private boolean BASELINE_ON_MIGRATE;

    public FlywayConfig(Map<String, DataSource> dataSources) {
    
    
        this.dataSources = dataSources;
    }

    @Bean
    @PostConstruct
    public void migrateOrder() {
    
    
        List<String> locations = Arrays.stream(SQL_LOCATION.split(",")).collect(Collectors.toList());
        // 根据数据源分别去构建属于这个数据源的locations的路径,用这个map保存,key:数据源名称。value:迁移脚本所在的路径
        Map<String, List<String>> flywayLocals = new HashMap<>();
        locations.forEach(location -> dataSources.forEach((k, v) -> {
    
    
            String s = location + "/" + k;
            List<String> flyways = flywayLocals.getOrDefault(k, new ArrayList<>());
            flyways.add(s);
            flywayLocals.put(k, flyways);
        }));
        //真正的触发迁移执行在这个循环里
        flywayLocals.forEach((k, v) -> {
    
    
            Flyway flyway = Flyway.configure()
                    .dataSource(dataSources.get(k))
                    .locations(v.toArray(new String[0]))
                    .baselineOnMigrate(BASELINE_ON_MIGRATE)
                    .load();
            flyway.migrate();
        });
    }
}

运行结果

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42380504/article/details/130580241
今日推荐