在Java中所有的连接池都按照规范实现DataSource接口,在获取连接的时候即可通过getConnection()获取连接而不用关心底层究竟是何数据库连接池。
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password) throws SQLException;
}
在大多数系统中我们只需要一个数据源,而现在WEB系统通常是Spring为基石。不管你是xml配置,javaBean配置还是yml,properties配置文件配置,其核心就是注入一个数据源交给spring的进行管理。
而在部分系统中我们可能会面临一些情况,连接多个表,主从,甚至多个不同的库等等情况,核心需求就是我们可能需要配置多个连接池。
在mybatis系统中我们使用多数据源可以配置配置多个DataSource,SqlSessionFactory,SqlSessionTemplate,然后在xml和mapper也分开管理。具体可以参考https://blog.csdn.net/neosmith/article/details/61202084 这篇博客。
这种方案在小的系统足够使用,作者认为更适合于多个不同的数据库。
回归正题,在Spring中从2.0.1版本默认提供了AbstractRoutingDataSource,我们继承它实现相关方法,把所有需要的数据源设置进去即可动态的切换数据源。我们可以看下核心方法的源码。
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
//设置所有的数据源
private Map
以上具体的实现可以参考这篇博客 https://blog.csdn.net/u012881904/article/details/77449710
需要注意的是AOP的order必须在事物的order之前。
优点:方便配置,便捷使用。
缺点:默认实现有一定局限性,大多数人足够使用。
如果你有更复杂的使用场景,多库数据源,分组数据源,多主多从等等较复杂场景可以尝试
https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter
一个基于springboot的快速集成多数据源的启动器。
一个标准的主从的配置如下,引入相关配置即可使用。更多使用的方式查看相关文档。
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master,如果你主从默认下主库的名称就是master可不定义此项。
datasource:
master:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://47.100.20.186:3306/dynamic?characterEncoding=utf8&useSSL=false
slave_1:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://47.100.20.186:3307/dynamic?characterEncoding=utf8&useSSL=false
slave_2:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://47.100.20.186:3308/dynamic?characterEncoding=utf8&useSSL=false
实现核心源码如下
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
/**
* 所有库
*/
private Map dataSourceMap;
/**
* 分组数据库
*/
private Map groupDataSources = new HashMap<>();
@Setter
private DynamicDataSourceProvider dynamicDataSourceProvider;
@Setter
private Class extends DynamicDataSourceStrategy> dynamicDataSourceStrategyClass;
/**
* 默认数据源名称,默认master,可为组数据源名,可为单数据源名
*/
@Setter
private String primary;
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceLookupKey();
}
@Override
protected DataSource determineTargetDataSource() {
String lookupKey = (String) determineCurrentLookupKey();
if (groupDataSources.containsKey(lookupKey)) {
log.debug("从 {} 组数据源中返回数据源", lookupKey);
return groupDataSources.get(lookupKey).determineDataSource();
} else if (dataSourceMap.containsKey(lookupKey)) {
log.debug("从 {} 单数据源中返回数据源", lookupKey);
return dataSourceMap.get(lookupKey);
}
log.debug("从默认数据源中返回数据");
return groupDataSources.containsKey(primary) ? groupDataSources.get(lookupKey).determineDataSource() : dataSourceMap.get(primary);
}
@Override
public void afterPropertiesSet() {
this.dataSourceMap = dynamicDataSourceProvider.loadDataSources();
log.debug("共加载 {} 个数据源", dataSourceMap.size());
//分组数据源
for (Map.Entry dsItem : dataSourceMap.entrySet()) {
String dsName = dsItem.getKey();
if (dsName.contains("_")) {
String[] groupDs = dsName.split("_");
String groupName = groupDs[0];
DataSource dataSource = dsItem.getValue();
if (groupDataSources.containsKey(groupName)) {
groupDataSources.get(groupName).addDatasource(dataSource);
} else {
try {
DynamicGroupDatasource groupDatasource = new DynamicGroupDatasource(groupName, dynamicDataSourceStrategyClass.newInstance());
groupDatasource.addDatasource(dataSource);
groupDataSources.put(groupName, groupDatasource);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//检测组数据源设置
Iterator> groupIterator = groupDataSources.entrySet().iterator();
while (groupIterator.hasNext()) {
Map.Entry item = groupIterator.next();
log.debug("组 {} 下有 {} 个数据源", item.getKey(), item.getValue().size());
if (item.getValue().size() == 1) {
log.warn("请注意不要设置一个只有一个数据源的组,{} 组将被移除", item.getKey());
groupIterator.remove();
}
}
//检测默认数据源设置
if (groupDataSources.containsKey(primary)) {
log.debug("当前的默认数据源是组数据源,组名为 {} ,其下有 {} 个数据源", primary, groupDataSources.size());
} else if (dataSourceMap.containsKey(primary)) {
log.debug("当前的默认数据源是单数据源,数据源名为{}", primary);
} else {
throw new RuntimeException("请检查primary默认数据库设置,当前未找到" + primary + "数据源");
}
}
}