1. 程式人生 > 實用技巧 >springboot多資料來源優雅整合mybatis

springboot多資料來源優雅整合mybatis

基於springboot自動裝備機制,單資料來源的情況下我們通過配置資料庫連線資訊及引入對應mybatis-starter包,即可完成對資料庫的整合,然後開開心心寫業務,然而對於業務複雜的應用或者對舊系統進行改造時單應用往往需要整合多個數據源,這個時候資料來源的管理就沒那麼容易了.

常規做法

1、在application.yml配置多個數據源的連線資訊,類似這樣

spring:
  datasource:
    minIdle: 10
    maxActive: 50
    maxLifetime: 180000
    idleTimeout: 60000
  datasource0:
    url: jdbc:mysql://localhost:3339/db01?useSSL=false&useUnicode=yes&characterEncoding=utf8
    username: root
    password: 123456
    driverClassName: com.mysql.jdbc.Driver
  datasource1:
    url: jdbc:mysql://localhost:3340/db01?useSSL=false&useUnicode=yes&characterEncoding=utf8
    username: root
    password: 123456
    driverClassName: com.mysql.jdbc.Driver

2、配置資料來源

    @Value("${spring.datasource0.url}")
    private String url0;
    @Value("${spring.datasource0.username}")
    private String username0;
    @Value("${spring.datasource0.password}")
    private String password0;
    @Value("${spring.datasource0.driverClassName}")
    private String driverClassName0;

    @Value("${spring.datasource1.url}")
    private String url1;
    @Value("${spring.datasource1.username}")
    private String username1;
    @Value("${spring.datasource1.password}")
    private String password1;
    @Value("${spring.datasource1.driverClassName}")
    private String driverClassName1;
    @Bean("dataSource0")
    public DataSource dataSource0() {
        return initDataSource(url0,username0,password0,driverClassName0);
    }

    @Bean("dataSource1")
    public DataSource dataSource1() {
        return initDataSource(url1,username1,password1,driverClassName1);
    }

3、新增各個資料來源的mybatis配置,其中還需要不同資料來源下mapper檔案分開在不同的package下,類似這樣

@Configuration
@MapperScan(basePackages = "com.example.dao0" ,sqlSessionFactoryRef="sqlSessionFactory0" )
@EnableTransactionManagement
public class MyBatisConfig0 {


    @Autowired
    @Qualifier("dataSource0")
    private DataSource dataSource;

    @Bean("sqlSessionFactory0")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTypeAliasesPackage("com.example.entity");
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources("classpath*:mapper0/*.xml"));
        return bean.getObject();
    }

    @Bean("platformTransactionManager0")
    public PlatformTransactionManager platformTransactionManager(){
        return new DataSourceTransactionManager(dataSource);
    }

}
@Configuration
@MapperScan(basePackages = "com.example.dao1" ,sqlSessionFactoryRef="sqlSessionFactory1" )
@EnableTransactionManagement
public class MyBatisConfig1 {


    @Autowired
    @Qualifier("dataSource1")
    private DataSource dataSource;

    @Bean("sqlSessionFactory1")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTypeAliasesPackage("com.example.entity");
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources("classpath*:mapper1/*.xml"));
        return bean.getObject();
    }

    @Bean("platformTransactionManager1")
    public PlatformTransactionManager platformTransactionManager(){
        return new DataSourceTransactionManager(dataSource);
    }

}

優雅配置

  常規配置可以解決多資料來源的問題,但是也產生了很多限制,例如mapper檔案分包,每個datasouce都需要單獨的配置資訊等.

1、連線資訊配置

  基於讀取json檔案及spring FactoryBean將db連線資訊放入context上下文中.

json檔案

  此處可以將公司所有的資料來源都加入配置,然後再application.yml中配置具體使用哪幾個資料來源
{
  "mysql01" : {
    "dbName" : "db01",
    "dbType" : "mysql",
    "port" : "3306",
    "username" : "root",
    "host" : "127.0.0.1",
    "password" : "123456"
  },
   "mysql02" : {
     "dbName" : "db02",
     "dbType" : "mysql",
     "port" : "3306",
     "username" : "root",
     "host" : "127.0.0.1",
     "password" : "123456"
   }
 }

相關java配置類

@Data
public class DbItem {
    @JsonProperty("dbName")
    private String dbName;
    @JsonProperty("dbType")
    private String dbType;
    @JsonProperty("host")
    private String host;
    @JsonProperty("port")
    private String port;
    @JsonProperty("username")
    private String username;
    @JsonProperty("password")
    private String password;

    public String getUrl() {
        return String.format(DbConfig.DB_TYPE_MAP.get(dbType).getKey(), host, port, dbName);
    }

    public String getDriverClassName() {
        return DbConfig.DB_TYPE_MAP.get(dbType).getValue();
    }

}
public class DbConfig {
    public DbConfig() {

    }
    protected static final Map<String, Pair<String, String>> DB_TYPE_MAP = new HashMap<String, Pair<String, String>>();
    static {
        //新增mysql連線配置,可以繼續新增以支援其它資料庫
        DB_TYPE_MAP.put("mysql", new Pair("jdbc:mysql://%s:%s/%s?characterEncoding=utf8&useSSL=false",
                "com.mysql.jdbc.Driver"));
    }
    private Map<String, DbItem> dbMap;

    public DbConfig(Map<String, DbItem> dbMap) {
        super();
        this.dbMap = dbMap;
    }

    public DbItem getDb(String key) {
        return dbMap.get(key);
    }

}
@Component
public class DbCfgFactoryBean implements FactoryBean<DbConfig> {

    @Value("${dbcfg.filepath}")
    private String dbcfgpath;

    private DbConfig dbConfig;

    public DbConfig getObject() throws Exception {
      return createDbConfig();
    }

    public Class<?> getObjectType() {
        return DbConfig.class;
    }

    public boolean isSingleton() {
        return true;
    }

    private DbConfig createDbConfig(){
        Reader reader = null;
        ResourceLoader loader = new DefaultResourceLoader();
        EncodedResource encodedResource = new EncodedResource(loader.getResource(dbcfgpath),"UTF-8");
        try{
            reader = encodedResource.getReader();
            load(reader);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return dbConfig;
    }
    private void load(Reader reader) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String,DbItem> map = objectMapper.readValue(reader, new TypeReference<Map<String, DbItem>>() {
        });
        dbConfig = new DbConfig(map);
    }
}

2、資料來源配置

  使用註解+spring動態資料來源來實現資料來源動態切換
public class DataSourceConstant {

    public static final String DEFAULT_DATASOURCE = "dataSource01";

    public static final String DATASOURCE_02 = "dataSource02";
}
server:
  port: 8082
dbcfg:
  filepath: classpath:dbcfg.conf
datasource:
  db01: mysql01
  db02: mysql02
spring:
  datasource:
    minIdle: 10
    maxActive: 50
    maxLifetime: 1800
    idleTimeout: 600
@Configuration
public class DataSourceConfig {

    @Autowired
    private DbConfig dbConfig;

    @Value("${spring.datasource.minIdle:10}")
    private int minIdle;

    @Value("${spring.datasource.maxActive:50}")
    private int maxActive;
    @Value("${spring.datasource.maxLifetime}")
    private int maxLifetime;
    @Value("${spring.datasource.idleTimeout}")
    private int idleTimeout;

    @Value("${datasource.db02}")
    private String dataSource02;

    @Value("${datasource.db01}")
    private String dataSource01;


    @Bean("dataSource01")
    public DataSource dataSource01(){
        return initDataSource(dataSource01);
    }

    @Bean("dataSource02")
    public DataSource dataSource02(){
        return initDataSource(dataSource02);
    }

    @Bean("multipleDataSource")
    public DataSource multipleDataSource(@Qualifier("dataSource01") DataSource dataSource01,
                                         @Qualifier("dataSource02") DataSource dataSource02) {
        Map<Object, Object> datasources = new HashMap<Object, Object>();
        datasources.put(DataSourceConstant.DEFAULT_DATASOURCE, dataSource01);
        datasources.put(DataSourceConstant.DATASOURCE_02, dataSource02);
        MultipleDataSource multipleDataSource = new MultipleDataSource();
        multipleDataSource.setDefaultTargetDataSource(dataSource01);
        multipleDataSource.setTargetDataSources(datasources);
        return multipleDataSource;
    }

    private DataSource initDataSource(String dbName) {
        HikariDataSource datasource = new HikariDataSource();
        datasource.setJdbcUrl(dbConfig.getDb(dbName).getUrl());
        datasource.setUsername(dbConfig.getDb(dbName).getUsername());
        datasource.setPassword(dbConfig.getDb(dbName).getPassword());
        datasource.setDriverClassName(dbConfig.getDb(dbName).getDriverClassName());
        datasource.setMaximumPoolSize(maxActive);
        datasource.setMinimumIdle(minIdle);
        datasource.setMaxLifetime(maxLifetime);
        datasource.setIdleTimeout(idleTimeout);
        datasource.setConnectionTestQuery("select 1");
   }
}
public class MultipleDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> dataSourceName = new InheritableThreadLocal<String>();

    public static void setDataSourceKey(String dataSource) {
        dataSourceName.set(dataSource);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return dataSourceName.get();
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface DataSource {
    String value();
}

3、mybatis配置

@Configuration
@MapperScan("com.example.dao")
public class MybatisConfig implements TransactionManagementConfigurer {


    @Autowired
    @Qualifier("multipleDataSource")
    private DataSource multipleDataSource;

    @Bean("sqlSessionFactoryBean")
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(multipleDataSource);
        bean.setTypeAliasesPackage("com.example.entity");
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
        return bean.getObject();

    }

    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(multipleDataSource);
    }
}