1.数据源管理
- 使用
com.baomidou.dynamic.datasource.AbstractRoutingDataSource抽象类统一管理数据源,继承自spring-jdbc的org.springframework.jdbc.datasource.AbstractDataSource - 实现类
com.baomidou.dynamic.datasource.DynamicRoutingDataSource - 初始化调用
public synchronized void addDataSource(String ds, DataSource dataSource)加载数据源,数据源存进dataSourceMap中。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
@Slf4j public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean { @Setter private DynamicDataSourceProvider provider;
public synchronized void addDataSource(String ds, DataSource dataSource) { if (p6spy) { dataSource = new P6DataSource(dataSource); } dataSourceMap.put(ds, dataSource); if (ds.contains(UNDERLINE)) { String group = ds.split(UNDERLINE)[0]; if (groupDataSources.containsKey(group)) { groupDataSources.get(group).addDatasource(dataSource); } else { try { DynamicGroupDataSource groupDatasource = new DynamicGroupDataSource(group, strategy.newInstance()); groupDatasource.addDatasource(dataSource); groupDataSources.put(group, groupDatasource); } catch (Exception e) { log.error("添加数据源失败", e); dataSourceMap.remove(ds); } } } log.info("动态数据源-加载 {} 成功", ds); } @Override public void afterPropertiesSet() throws Exception { Map<String, DataSource> dataSources = provider.loadDataSources(); log.info("初始共加载 {} 个数据源", dataSources.size()); for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) { addDataSource(dsItem.getKey(), dsItem.getValue()); } if (groupDataSources.containsKey(primary)) { log.info("当前的默认数据源是组数据源,组名为 {} ,其下有 {} 个数据源", primary, groupDataSources.get(primary).size()); } else if (dataSourceMap.containsKey(primary)) { log.info("当前的默认数据源是单数据源,数据源名为 {}", primary); } else { throw new RuntimeException("请检查primary默认数据库设置"); } } }
|
上方只保留核心源码
DynamicRoutingDataSource实现了InitializingBean接口,会在配置文件加载成功后自动调用afterPropertiesSet- 调用
DynamicDataSourceProvider.loadDataSources()加载配置文件
2.数据操作-拦截@DS
进行数据操作时,方法会被com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
private static final String DYNAMIC_PREFIX = "#"; @Setter private DsProcessor dsProcessor; private DynamicDataSourceClassResolver dynamicDataSourceClassResolver = new DynamicDataSourceClassResolver(); @Override public Object invoke(MethodInvocation invocation) throws Throwable { try { DynamicDataSourceContextHolder.push(determineDatasource(invocation)); return invocation.proceed(); } finally { DynamicDataSourceContextHolder.poll(); } } private String determineDatasource(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); Class<?> declaringClass = dynamicDataSourceClassResolver.targetClass(invocation); DS ds = method.isAnnotationPresent(DS.class) ? method.getAnnotation(DS.class) : AnnotationUtils.findAnnotation(declaringClass, DS.class); String key = ds.value(); return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key; } }
|
- 拦截器首先从被拦截的方法或者类(一般@DS注解用于Service,也可用于Mapper和Controller)上寻找
@DS注解 - 获取到
@DS注解的值后将其存入com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder DynamicDataSourceContextHolder使用ThreadLocal存储当前线程的数据源名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
public final class DynamicDataSourceContextHolder {
@SuppressWarnings("unchecked") private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() { @Override protected Object initialValue() { return new ArrayDeque(); } }; private DynamicDataSourceContextHolder() { }
public static String peek() { return LOOKUP_KEY_HOLDER.get().peek(); }
public static void push(String ds) { LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds) ? "" : ds); }
public static void poll() { Deque<String> deque = LOOKUP_KEY_HOLDER.get(); deque.poll(); if (deque.isEmpty()) { LOOKUP_KEY_HOLDER.remove(); } }
public static void clear() { LOOKUP_KEY_HOLDER.remove(); } }
|
3.数据操作-获取数据库连接
调用org.springframework.jdbc.datasource.getConnection()方法;getConnection()方法最终调用了com.baomidou.dynamic.datasource.AbstractRoutingDataSource的getConnection()方法;
1 2 3 4
| @Override public Connection getConnection() throws SQLException { return determineDataSource().getConnection(); }
|
determineDataSource()由子类com.baomidou.dynamic.datasource.DynamicRoutingDataSource实现,可以看到DynamicRoutingDataSource从DynamicDataSourceContextHolder获取数据源名称,这个在之前拦截器处理存进ThreadLocal中,如果有数据源名称则从dataSourceMap中获取,没有则获取默认的primary数据源。
==此时的数据源已经切换成了我们需要的数据源。==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Override public DataSource determineDataSource() { return getDataSource(DynamicDataSourceContextHolder.peek()); }
public DataSource getDataSource(String ds) { if (StringUtils.isEmpty(ds)) { return determinePrimaryDataSource(); } else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) { log.debug("从 {} 组数据源中返回数据源", ds); return groupDataSources.get(ds).determineDataSource(); } else if (dataSourceMap.containsKey(ds)) { log.debug("从 {} 单数据源中返回数据源", ds); return dataSourceMap.get(ds); } if (strict) { throw new RuntimeException("不能找到名称为" + ds + "的数据源"); } return determinePrimaryDataSource(); }
|
4.清除当前数据源
数据操作完成后,方法返回第二步中的拦截器,执行DynamicDataSourceContextHolder.poll();清除掉此次的数据源,避免影响后续数据操作。
1 2 3 4 5 6 7 8 9
| @Override public Object invoke(MethodInvocation invocation) throws Throwable { try { DynamicDataSourceContextHolder.push(determineDatasource(invocation)); return invocation.proceed(); } finally { DynamicDataSourceContextHolder.poll(); } }
|
5.相关依赖
1 2 3 4 5
| <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>2.5.6</version> </dependency>
|
6.示例yml配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| spring: application: name: datasource: dynamic: primary: dataSource1 datasource: dataSource1: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver url: jdbc:sqlserver://localhost:1433;database=dataSource1 username: password: dataSource2: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver url: jdbc:sqlserver://localhost:1433;instanceName=sqlserver2017;DatabaseName=dataSource2 username: password:
|
7.注意事项
一个事务方法(方法被@Transactional修饰),如果出现对多个数据源进行操作的情况,子方法用@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)修饰,参考Spring事务传播机制
上述配置含义为,将主方法事务暂时挂起,开启新事务去执行子方法