解决问题
- 多个数据源使用注解切换。
- 与连接池的集成
- 分布式事务
实现方式
- 基于abstractRoutingDataSource做拓展
- 不同操作类,固定数据源
- 分库分表中间件
使用dynamic-datasource-spring-boot-starter
采用的是方式一的实现方式
大面上的源码流程
- 自动装配类com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration。 首先将配置信息加载到DynamicDataSourceProvider中。
@Bean
@Order(0)
public DynamicDataSourceProvider ymlDynamicDataSourceProvider() {
return new YmlDynamicDataSourceProvider(properties.getDatasource());
}
- 实例化DataSource实现类DynamicRoutingDataSource
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
return dataSource;
}
- DynamicRoutingDataSource实现了InitializingBean,在属性注入完成后调用AfterPropertiesSet()方法。主要是将dynamicDataSourceProvider的数据按照分组添加到DynamicRoutingDataSource的所有数据库实例变量
Map<String,DataSource> dataSourceMap和分组数据库实例变量Map<String,DataSource> groupDataSourceMap
// 添加并分组数据源
Map<String, DataSource> dataSources = new HashMap<>(16);
for (DynamicDataSourceProvider provider : providers) {
dataSources.putAll(provider.loadDataSources());
}
for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
addDataSource(dsItem.getKey(), dsItem.getValue());
}
- com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor#invoke编程式切面,解析使用@DS注解的方法,获取注解的value值决定使用哪个数据源。将数据源名称存入DynamicDataSourceContextHolder中。
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String dsKey = determineDatasourceKey(invocation);
DynamicDataSourceContextHolder.push(dsKey);
try {
return invocation.proceed();
} finally {
DynamicDataSourceContextHolder.poll();
}
}
- mybatis执行SQL语句要调用getConnection获取数据库连接对象com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource#getConnection()。调用实现类的com.baomidou.dynamic.datasource.DynamicRoutingDataSource#determineDataSource方法。从上下文取出数据源名称,根据名称获得数据源
@Override
public DataSource determineDataSource() {
String dsKey = DynamicDataSourceContextHolder.peek();
return getDataSource(dsKey);
}
通过数据源解析器解析要切换到哪个数据源,不是简单的获取注解的value值,要考虑与mybatis适配
private String determineDatasourceKey(MethodInvocation invocation) {
String key = dataSourceClassResolver.findKey(invocation.getMethod(), invocation.getThis());
return key.startsWith(DYNAMIC_PREFIX) ? dsProcessor.determineDatasource(invocation, key) : key;
}
本地多数据源事务解决方案
在很多数据库相关项目里,connection这个词是有歧义的,可能有的含义包括:事务、会话、数据库连接。 spring自带事务明显默认了一个事务会话就是一个数据库链接这种老思想。从而导致了数据源回滚只能回滚第一个,后面的都失效了。
- 注解@DSTransactioonal的增强类com.baomidou.dynamic.datasource.aop.DynamicLocalTransactionInterceptor#invoke生成xid绑定到上下文中
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (!StringUtils.isEmpty(TransactionContext.getXID())) {
return methodInvocation.proceed();
}
boolean state = true;
Object o;
LocalTxUtil.startTransaction();
try {
o = methodInvocation.proceed();
} catch (Exception e) {
state = false;
throw e;
} finally {
if (state) {
LocalTxUtil.commit();
} else {
LocalTxUtil.rollback();
}
}
return o;
}
- sql执行完成,没有问题就要提交事务LocalTxUtil.commit();ConnectionFactory是记录了当前会话中所有用到的连接,也就是Connection的代理类ConnectionProxy。notify方法通知每一个当前会话的连接是提交还是回滚。
public static void commit() throws Exception {
try {
ConnectionFactory.notify(true);
} finally {
log.debug("dynamic-datasource commit local tx [{}]", TransactionContext.getXID());
TransactionContext.remove();
}
}
- ConnectionProxy是在什么时候加入到连接中的呢,是在getConnection的时候。执行SQL之前会执行getConnection方法,如果service层方法添加了@DSTransactional注解,TransactionContext。getXID不为空,如果上下文连接工厂ConnectionFactory没有这个数据源,就新创建一个ConnectionProxy并添加到当前上下文中。这样这个事务中就包括了所有相关联的连接
@Override
public Connection getConnection() throws SQLException {
String xid = TransactionContext.getXID();
if (StringUtils.isEmpty(xid)) {//没有使用注解
return determineDataSource().getConnection();
} else {//使用了@DSTransactinal注解
String ds = DynamicDataSourceContextHolder.peek();
ds = StringUtils.isEmpty(ds) ? getPrimary() : ds;
ConnectionProxy connection = ConnectionFactory.getConnection(ds);
return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;
}
}
工具类
AnnotatedElementUtiles
org.springframework.core.annotation.AnnotatedElementUtils好像是个获取方法上注解的类,特殊作用目前不理解
获取类的代理对象
private OrderService self() {
return (OrderService) AopContext.currentProxy();
}