1. 动态数据源的使用示例
@Service
public class SysConfigServiceImpl implements ISysConfigService {
// DataSourceType.MASTER表示使用主数据源
@DataSource(DataSourceType.MASTER)
public SysConfig selectConfigById(Long configId)
{
SysConfig config = new SysConfig();
config.setConfigId(configId);
return configMapper.selectConfig(config);
}
}
2. 数据源配置类
@Configuration
public class DruidConfig
{
@Bean
// application-druid.yml配置文件中,spring.datasource.druid.master下的url、
// username、password会设置到DruidDataSource的父类DruidAbstractDataSource对应的属性中
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties)
{
// 创建DruidDataSource(DruidDataSource会使用Druid连接池)
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean
// application-druid.yml配置文件中,spring.datasource.druid.slave下的url、
// username、password会设置到DruidDataSource的父类DruidAbstractDataSource对应的属性中
@ConfigurationProperties("spring.datasource.druid.slave")
// 当spring.datasource.druid.slave.enabled为true时,才会创建该bean
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties)
{
// 创建DruidDataSource(DruidDataSource会使用Druid连接池)
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean(name = "dynamicDataSource")
// @Primary:当有多个同类型bean时,该bean生效(因此这里创建了三个DataSource类型的bean,名为dynamicDataSource的bean先生效)
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource)
{
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
// 从数据源的方法名是slaveDataSource(方法名即beanName)
setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
// 将key为MASTER,value为主数据源、key为SLAVE,value为从数据源的数据添加到targetDataSources中
// 创建自定义的动态数据源类
return new DynamicDataSource(masterDataSource, targetDataSources);
}
// 其余(略)
}
3. 自定义数据源类
// 继承了AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource
{
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
{
// 将主数据源设置为默认数据源
super.setDefaultTargetDataSource(defaultTargetDataSource);
// 解析、设置所有数据源
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
// AbstractRoutingDataSource的getConnection方法最终会调用该方法,获取要使用的数据源key,并通过该key确定要使用的数据源
@Override
protected Object determineCurrentLookupKey()
{
// 从ThreadLocal中获取要使用的数据源key
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
4. 数据源切面
@Aspect
@Order(10)
@Component
public class DataSourceAspect
{
protected Logger logger = LoggerFactory.getLogger(getClass());
// @DataSource注解的切面
@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
+ "|| @within(com.ruoyi.common.annotation.DataSource)")
public void dsPointCut()
{
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable
{
// 获取DataSource注解
DataSource dataSource = getDataSource(point);
if (StringUtils.isNotNull(dataSource))
{
// 将要使用的数据源key设置到ThreadLocal中(方法执行前)
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try
{
return point.proceed();
}
finally
{
// 移除当前线程绑定的数据源key(方法执行后)
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point)
{
// 获取DataSource注解
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource))
{
return dataSource;
}
// 从signature.getDeclaringType()实现的接口、继承的父类中寻找
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}