SpringBoot學習筆記(15):動態數據源切換
SpringBoot學習筆記(15):動態數據源切換
數據源
Java的javax.sql.DataSource接口提供了一種處理數據庫連接的標準方法。通常,DataSource使用URL和一些憑據來建立數據庫連接。
SpringBoot默認提供了針對內存數據庫的數據源,如H2、hqldb、Derby等。
配置數據源信息
當我們引入spring-boot-start-jdbc時,SpringBoot會默認使用其綁定的Tomcat的數據源。
DataSource可由spring.datasource.*中的外部配置屬性控制。例如,您可以在application.properties中聲明以下部分:
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
當然,我們也可以是使用基於Java代碼的配置方法:
@Bean public DataSource mysqlDataSource(){ DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/demo"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; }
使用第三方數據源
有時候,我們需要使用第三方的數據源,如Druid、C3P0等,在SpringBoot中也很簡單。
以Druid為例,首先添加Maven依賴:
<dependencies> <!-- 添加MySQL依賴 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 添加JDBC依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 添加Druid依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> </dependencies>
配置數據庫連接信息即可:
@Configuration
public class DataSourceConfig {
@Autowired
private Environment env;
@Bean
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
}
動態新增數據源
有時候,我們需要在程序運行過程中動態增加或改變數據源。
改變數據源
Spring2.0 引入了AbstractRoutingDataSource,該類可以充當DataSource的路由中介,能在運行時,動態切換當前數據源。如下為其核心代碼,可見他是根據determineCurrentLookupKey()獲取Key值,來從resolvedDataSources中找到要切換的數據源。
private Map<Object, Object> targetDataSources;
private Map<Object, DataSource> resolvedDataSources;
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
而這個determineCurrentLookupKey函數需要我們自已來實現,所以我們要定義一個子類DynamicRoutingDataSource來繼承AbstractRoutingDataSource,然後實現這個方法,其實就是返回一個Key字符串!。
為了保證線程安全,我們可以把數據源的Key信息保存在ThreadLocal中,如下:
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "dynamic_db0";
}
};
/**
* To switch DataSource
*
* @param key the key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* Get current DataSource
*
* @return data source key
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* To set DataSource as default
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
這樣,determineCurrentLookupKey就可以如下定義:
@Override
protected Object determineCurrentLookupKey() {
System.out.println("Current DataSource is [{}]"+ DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
所以我們在使用時,只需要使用DynamicDataSourceContextHolder.setDataSourceKey(),並可以切換數據源。
新增數據源
新增數據源,其實就是更新targetDataSources字段,一個簡單的Demo如下:
/**
* 動態增加數據源
*
* @param map 數據源屬性
* @return
*/
public synchronized boolean addDataSource(Map<String, String> map) {
try {
String database = map.get("database");//獲取要添加的數據庫名
if (database==null||database.equals("")) return false;
if (DynamicRoutingDataSource.isExistDataSource(database)) return true;
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl(map.get("url"));
dataSource.setUsername(map.get("username"));
dataSource.setPassword(map.get("password"));
Map<Object, Object> targetMap = DynamicRoutingDataSource.targetDataSources;
targetMap.put(database, dataSource);
// 當前 targetDataSources 與 父類 targetDataSources 為同一對象 所以不需要set
// this.setTargetDataSources(targetMap);
this.afterPropertiesSet();
} catch (Exception e) {
logger.error(e.getMessage());
return false;
}
return true;
}
配置動態數據源
我們可以將其以Bean形式配置,並在其中設置默認數據源。
@Bean("dynamicDataSource")
public DynamicRoutingDataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/demo");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSourceMap.put("DEMO", dataSource);
dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource);// 設置默認數據源
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
return dynamicRoutingDataSource;
}
之後,我們在代碼中便可以執行新增數據源、切換數據源操作等等。
參考文檔:
- https://blog.csdn.net/pengjunlee/article/details/80081231
- https://www.jianshu.com/p/0a485c965b8b
SpringBoot學習筆記(15):動態數據源切換