Springboot 多数据源记录

288 阅读3分钟

参考  juejin.cn/post/684490…

  背景: 实现的项目有一个多数据源的功能需求,我的设想是项目中配置一个数据源的配置库,里面有一张表存数据源的配置,其余的数据源都是根据配置表自动生成的。

   实现: 

  1. 搭建环境

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"     
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">   
 <modelVersion>4.0.0</modelVersion>   
 <groupId>org.example</groupId>   
 <artifactId>FFAPIJson</artifactId>   
 <version>1.0-SNAPSHOT</version>  
  <parent>       
 <groupId>org.springframework.boot</groupId>      
  <artifactId>spring-boot-starter-parent</artifactId>      
  <version>2.2.2.RELEASE</version>      
  <relativePath/> <!-- lookup parent from repository -->  
  </parent>  
  <!--     基于Springboot 2.0  及 json 开发 -->    
<dependencies>       
 <!--spring boot-->       
 <dependency>           
 <groupId>org.springframework.boot</groupId>     
       <artifactId>spring-boot-starter-web</artifactId> 
       </dependency>  
      <dependency>       
     <groupId>org.springframework.boot</groupId> 
           <artifactId>spring-boot-configuration-processor</artifactId>  
      </dependency>      
  <dependency>        
    <groupId>org.springframework.boot</groupId>    
        <artifactId>spring-boot-starter-jdbc</artifactId>    
    </dependency>     
   <dependency>            
<groupId>org.springframework.boot</groupId>   
         <artifactId>spring-boot-starter-aop</artifactId>      
  </dependency>      
  <!--mysql 驱动-->     
   <dependency>           
 <groupId>mysql</groupId>       
     <artifactId>mysql-connector-java</artifactId>      
      <scope>runtime</scope>       
 </dependency>       
 <!--mybatis-plus-->   
     <dependency>          
  <groupId>com.baomidou</groupId>      
      <artifactId>mybatis-plus-boot-starter</artifactId>     
       <version>3.3.0</version>       
 </dependency>        
<dependency>           
 <groupId>org.springframework.boot</groupId> 
           <artifactId>spring-boot-starter-test</artifactId>      
      <scope>test</scope>           
 <exclusions>             
   <exclusion>             
       <groupId>org.junit.vintage</groupId>  
                  <artifactId>junit-vintage-engine</artifactId>  
              </exclusion>           
 </exclusions>      
  </dependency>       
 <!--  Hutool-->      
  <dependency>          
  <groupId>cn.hutool</groupId>   
         <artifactId>hutool-all</artifactId>    
        <version>5.5.9</version>       
 </dependency>       
 <!-- faster-json-->        
<!--fastjson-->        
<dependency>           
 <groupId>com.alibaba</groupId>   
         <artifactId>fastjson</artifactId>         
   <version>1.2.62</version>        
</dependency>  
  </dependencies>
</project>

2. 多数据源配置

    1. 实体类

@TableName("mydatasource")
public class FfDataSource {  
  private Integer id;  
  @TableField("ip")  
  private String url; 
   private String port; 
   @TableField("ff_schema")   
 private String ffSchema;   
 @TableField("driver_name")    
private String driverName;   
 private String username;  
  private String password;   
 @TableField("key_name")   
 private String keyName;   
 @TableField("is_valid")   
 private boolean isValid;    
private LocalDateTime createTime; 
   private LocalDateTime updateTime;  
  private String creator;    
private String updator;
//------get  set 方法------
}

2. 数据表结构

CREATE TABLE `mydatasource` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'ip 127.0.0.1',
  `port` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '端口号 3306',
  `ff_schema` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '数据库名',
  `username` varchar(255) DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `driver_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '驱动',
  `key_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '唯一标识',
  `is_valid` tinyint DEFAULT '1' COMMENT '是否有效,默认有效',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `creator` varchar(255) DEFAULT NULL COMMENT '创建人',
  `updator` varchar(255) DEFAULT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

3. 创建主数据源

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@Configuration
@MapperScan(basePackages = "com.fivefu.core.mapper")
@Order(5)
public class DynamicDataSourceConfig {
    
    private static  String jdbcUrl;

    private static String username;
    private static String password;

    private static String driver;

    @Bean(DataSourceConstants.DS_KEY_MASTER)
    public static DataSource masterDataSource() {
        return DataSourceBuilder.create().url(jdbcUrl).username(username).password(password).driverClassName(driver).build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource() {
        Map<Object, Object> datasourceMap = new HashMap<>(1);
        datasourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
        return new FFDynamicDS(masterDataSource(), datasourceMap);
    }

   @Value("${spring.datasource.master.jdbc-url}")
    public  void setJdbcUrl(String jdbcUrl) {
        DynamicDataSourceConfig.jdbcUrl = jdbcUrl;
    }
    @Value("${spring.datasource.master.username}")
    public  void setUsername(String username) {
        DynamicDataSourceConfig.username = username;
    }
    @Value("${spring.datasource.master.password}")
    public  void setPassword(String password) {
        DynamicDataSourceConfig.password = password;
    }
    @Value("${spring.datasource.master.driver-class-name}")
    public  void setDriver(String driver) {
        DynamicDataSourceConfig.driver = driver;
    }
}

4. 实现多数据源

   多数据源实质上就是一个Map集合,需要哪个数据源的时候从Map中取即可,参考文章中讲的更详细,继承 AbStractRoutingDataSource (抽象路由数据源)即可实现。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    @Nullable
    private Map<Object, Object> targetDataSources;//数据源集合
    @Nullable
    private Object defaultTargetDataSource; //默认数据源 我们上面初始化的数据源就是设置成默认的

    private boolean lenientFallback = true;
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;
    @Nullable
    private DataSource resolvedDefaultDataSource;
 //----- 后面的不看---
}

,上面dynamicDataSource() 调用额的是下面的构造方法,通过@Bean注解加入到Spring环境里,@Order 是为了一个顺序,先加载默认数据眼,从默认数据源里加载其它数据源的配置,获得后加入到Map中。下面开始实现

public class FFDynamicDS  extends AbstractRoutingDataSource {

    private Map<Object,Object> backupTargetDataSources;

    public  FFDynamicDS(DataSource defaultDataSource,Map<Object,Object> targetDataSOurce){
         backupTargetDataSources = targetDataSOurce;
         super.setDefaultTargetDataSource(defaultDataSource);
         super.setTargetDataSources(backupTargetDataSources);
         super.afterPropertiesSet();
    }
    // 添加多个数据源
    public void addDataSource(List<FfDataSource> ffDataSources){
        for(FfDataSource item : ffDataSources){
            this.backupTargetDataSources
                    .put(item.getKeyName(), DataSourceUtil.makeNewDataSource(item));
        }

        //覆盖原来的多数据源
        super.setTargetDataSources(backupTargetDataSources);
        //这个相当于数据源的设置
        //AbstractRoutingDataSource  是实现了 InitializingBean
        //AbstractRoutingDataSource  实例化之后回调用afterPropertiesSet
        //通过这个方法 我们覆盖掉的多数据源才会配置
        super.afterPropertiesSet();
    }
    //决定当前数据源的锁 -- 我们用数据源的key
    @Override
    protected  Object determineCurrentLookupKey()
    {
        return DynamicDataSourceContextHolder.getContextKey();
    }


}

   //然后就是获得其它数据源了

@Component
@Order(10)
public class DemoInitDataSource implements ApplicationRunner {

    @Autowired
    FfDataSourceService ffDataSourceService;

    public void initDataSource() {
        QueryWrapper<FfDataSource> queryWrapper = new QueryWrapper<>();
        List<FfDataSource> list = ffDataSourceService.list(queryWrapper.eq("is_valid", 1));
        DataSourceUtil.addDataSourceListToDynamic(list);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("开始执行初始化数据源操作");
        initDataSource();

    }
}

这里Order(10)就是为了比初始化默认数据源慢,不然数据源还没加载好,先请求就会出错。

项目地址 : gitee.com/own\_3\_0/f…