动态数据源
动态数据源就是程序在运行过程中,可以在多个数据源之间切换,从而对不同的数据源进行操作。Spring Framework的Jdbc包中,提供了AbstractRoutingDataSource用于实现动态数据源。
实现
可以先自己设计一种动态数据源的实现:在一个数据源池中维护各个不同的数据源,当执行数据库操作时,从数据池中获取对应的数据源链接来进行数据库操作。这样看来我们只需要一个数据源池,而AbstractRoutingDataSource也正是基于这样的原理。
AbstractRoutingDataSource
示例
下面先来看一个示例,创建DynamicDataSource继承AbstractRoutingDataSource,并覆盖determineCurrentLookupKey()方法。
package com.jdbc;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
private static String dataSource;
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public DataSource getCurrentDataSource(){
return super.determineTargetDataSource();
}
public static String getDataSource() {
return dataSource;
}
public static void setDataSource(String dataSource) {
DynamicDataSource.dataSource = dataSource;
}
}
创建测试类DynamicDataSourceTest,其中维护了多个数据源,并实现了运行时在不同的数据源中进行操作。
package jdbc;
import com.jdbc.DynamicDataSource;
import com.sun.istack.internal.NotNull;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
public class DynamicDataSourceTest {
public static final String PRIMARY_DATASOURCE = "PRIMARY";
public static final String SECOND_DATASOURCE = "SECOND";
public static final String DRIVE_NAME = "com.mysql.cj.jdbc.Driver";
public static final String PRIMARY_URL = "jdbc:mysql://localhost/primary";
public static final String SECOND_URL = "jdbc:mysql://localhost/seconnd";
public static final String USER_NAME = "root";
public static final String PASSWORD = "123456";
public static final NamedDataSource primaryDataSoruce;
public static final NamedDataSource secondDataSource;
public static DynamicDataSource dynamicDataSource;
static {
primaryDataSoruce = new NamedDataSource(PRIMARY_DATASOURCE, PRIMARY_URL, USER_NAME, PASSWORD, DRIVE_NAME);
secondDataSource = new NamedDataSource(SECOND_DATASOURCE, SECOND_URL, USER_NAME, PASSWORD, DRIVE_NAME);
dynamicDataSource = new DynamicDataSource();
}
@Test
public void dynamicDataSourceTest(){
// 设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(primaryDataSoruce);
// 设置目标数据源
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(PRIMARY_DATASOURCE, primaryDataSoruce);
targetDataSources.put(SECOND_DATASOURCE, secondDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
// 解析数据源
dynamicDataSource.afterPropertiesSet();
selectUserByDataSource(PRIMARY_DATASOURCE);
selectUserByDataSource(SECOND_DATASOURCE);
}
public void selectUserByDataSource(String dataSource){
DynamicDataSource.setDataSource(dataSource);
doSelectUser();
}
public void doSelectUser(){
try {
NamedDataSource currentDataSource = (NamedDataSource) dynamicDataSource.getCurrentDataSource();
System.out.println("current DataSource is:" + currentDataSource.getDataSourceName());
Connection connection = currentDataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from sys_user where user_id = 1");
while (resultSet.next()){
System.out.println("password:" + resultSet.getString("password"));
}
resultSet.close();
statement.close();
connection.close();
} catch (Exception e){
e.printStackTrace();
}
}
}
class NamedDataSource extends DriverManagerDataSource{
private final String dataSourceName;
public NamedDataSource(@NotNull String dataSourceName, String url, String username, String password, String driveName) {
super(url, username, password);
super.setDriverClassName(driveName);
this.dataSourceName = dataSourceName;
}
public String getDataSourceName() {
return dataSourceName;
}
}
分析
AbstractRoutingDataSource作为一个数据源池,其中维护了多个数据源和默认数据源,首先要初始化这些数据源。在每次使用时,通多子类覆盖的setDataSource()方法设置当前需要时候的数据源,然后调用getCurrentDataSource()方法得到数据源,这样就完成了数据源的切换。
AbstractRoutingDataSource的部分签名如下:
// 默认数据源
@Nullable
private Object defaultTargetDataSource;
// 目标数据源
@Nullable
private Map<Object, Object> targetDataSources;
// 切换当前的数据源
@Nullable
protected abstract Object determineCurrentLookupKey();
在这其中还涉及到了事物封装的知识点,将在之后进行分析。
整合框架
通过代码实现动态数据源的功能实现了,那个在框架当中,动态数据源是如何实现的呢?
以MyBatis Plus为例,其也是实现了AbstractRoutingDataSource和DynamicRoutingDataSource用为维护多数据源,然后通过工具类DynamicDataSourceContextHolder实现不同数据源之间的切换。
总结
可以看到,动态数据源最主要还是【池】的概念,在池中维护多个数据源,想用哪个可以直接从里面拿。和线程池,数据源池的概念非常类似。