為什么需要多數據庫?
默認情況下,Spring Boot使用的是單數據庫配置(通過spring.datasource.*配置具體數據庫連接信息)。
對於絕大多數Spring Boot應用,這是符合其使用場景的,因為Spring Boot提倡的是微服務理念,每個應用對應一個單獨的業務領域。但在某些特殊情況下,一個應用對應多個數據庫又是無法避免的,例如實施數據庫分庫后原本單個數據庫變為多個數據庫。本文就結合實際代碼介紹如何在單個Boot應用中配置多數據庫,以及與之相關的Druid,jOOQ,Flyway等數據服務框架的配置改造。
配置示例

- DB1,DB2: 兩個示例數據庫
- ServiceA, ServiceB: 分別使用DB1和DB2的服務類
連接池Druid
Druid是阿里巴巴開源的數據庫連接池,提供了強大的監控支持,號稱Java語言中最好的連接池。
創建兩個配置類分別注冊對應DB1和DB2的DataSource Bean和TransactionManager Bean。以DB1為例:
Tip: 可以把其中一個配置類中注冊的DataSource Bean和DataSourceTransactionManager Bean加上@Primary注解,作為默認裝配實例。
// DB1
@Configuration
public class Db1Config {
@Bean(initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "db.db1")
public DataSource dataSource1() {
return new DruidDataSource();
}
@Bean
public DataSourceTransactionManager transactionManager1() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource1());
return transactionManager;
}
}
application.conf中的配置:
# DB1
db.db1.url=jdbc:mysql://127.0.0.1:3306/db1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
db.db1.username=root
db.db1.password=
ORM框架jOOQ
jOOQ是一個開源ORM框架,最大特點是提供類型安全的流式API,支持代碼生成。
參照Boot自帶的JooqAutoConfiguration,不難寫出如下配置類:
@Configuration
public class JooqConfig {
// DB1
@Bean
public DataSourceConnectionProvider dataSourceConnectionProvider1(
@Qualifier("dataSource1") DataSource dataSource1) {
return new DataSourceConnectionProvider(
new TransactionAwareDataSourceProxy(dataSource1));
}
@Bean
public SpringTransactionProvider transactionProvider1(
@Qualifier("transactionManager1") DataSourceTransactionManager txManager1) {
return new SpringTransactionProvider(txManager1);
}
// DB2
// ...
@Configuration
public static class DslContextConfig {
@Autowired(required = false)
private RecordMapperProvider recordMapperProvider;
@Autowired(required = false)
private Settings settings;
@Autowired(required = false)
private RecordListenerProvider[] recordListenerProviders;
@Autowired
private ExecuteListenerProvider[] executeListenerProviders;
@Autowired(required = false)
private VisitListenerProvider[] visitListenerProviders;
// DSLContext for DB1
@Bean
public DefaultDSLContext dslContext1(@Qualifier("dataSourceConnectionProvider1") DataSourceConnectionProvider connectionProvider1,
@Qualifier("transactionProvider1") SpringTransactionProvider transactionProvider1) {
return new DefaultDSLContext(configuration(connectionProvider1, transactionProvider1));
}
// DSLContext for DB2
// ...
private DefaultConfiguration configuration(ConnectionProvider connectionProvider, TransactionProvider transactionProvider) {
DefaultConfiguration configuration = new DefaultConfiguration();
configuration.setSQLDialect(SQLDialect.MYSQL);
configuration.set(connectionProvider);
configuration.set(transactionProvider);
if (this.recordMapperProvider != null) {
configuration.set(this.recordMapperProvider);
}
if (this.settings != null) {
configuration.set(this.settings);
}
configuration.set(this.recordListenerProviders);
configuration.set(this.executeListenerProviders);
configuration.set(this.visitListenerProviders);
return configuration;
}
}
}
服務類
配置好DataSource,TransacationManager和DSLContext之后,服務類的配置就比較簡單了,直接引用即可。注意由於存在多套Beans,需要通過@Qualifier注解指定裝配實例。
@Transactional("TransactionManager1")//每個事務指定 tx
public class ServiceA {
@Autowired
@Qualifier("dslContext1")
protected DSLContext dsl;
}
數據庫遷移框架Flyway
Flyway是一個輕量級的開源數據庫遷移框架,使用非常廣泛。
參照Boot自帶的FlywayAutoConfiguration,同樣可以寫出如下配置類:
@Configuration
public class FlywayConfig {
@Bean(initMethod = "migrate")
@ConfigurationProperties(prefix = "fw.db1")
public Flyway flyway(@Qualifier("dataSource1") DataSource dataSource1) {
Flyway clinic = new Flyway();
clinic.setDataSource(dataSource1);
return clinic;
}
// DB2
// ...
/**
* @see FlywayAutoConfiguration
*/
@Bean
@ConfigurationPropertiesBinding
public StringOrNumberToMigrationVersionConverter stringOrNumberMigrationVersionConverter() {
return new StringOrNumberToMigrationVersionConverter();
}
/**
* Convert a String or Number to a {@link MigrationVersion}.
* @see FlywayAutoConfiguration
*/
private static class StringOrNumberToMigrationVersionConverter
implements GenericConverter {
private static final Set<ConvertiblePair> CONVERTIBLE_TYPES;
static {
Set<ConvertiblePair> types = new HashSet<ConvertiblePair>(2);
types.add(new ConvertiblePair(String.class, MigrationVersion.class));
types.add(new ConvertiblePair(Number.class, MigrationVersion.class));
CONVERTIBLE_TYPES = Collections.unmodifiableSet(types);
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return CONVERTIBLE_TYPES;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
String value = ObjectUtils.nullSafeToString(source);
return MigrationVersion.fromVersion(value);
}
}
}
application.conf中的配置:
# DB1
fw.db1.enabled=true
關於事務
有經驗的同學馬上會問,多數據庫下事務會不會有問題?需要改造成分布式事務嗎?
只要為每個數據庫創建獨立的TransactionManager,就不會有問題,Spring會自動處理好事務的提交和回滾,就像單數據庫一樣。
至於分布式事務,大可不必,因為雖然有多個數據庫,但仍然屬於Local Transaction范疇。以后有時間我會再寫篇文章展開闡述一下。
總結
由上可見,無論是基礎的DataSource和TransactionManager,還是Spring之外的第三方框架,在Boot中基本都可以找到相應的AutoConfiguration配置類。參照這些配置類,就不難根據實際需要寫出自己的擴展版本。對於那些找不到AutoConfiguration配置類的,可結合框架的官方文檔,使用@Configuration和@Bean注解自行進行配置。
http://emacoo.cn/blog/spring-boot-multi-db

