问题:动态添加的数据源,在Service层调用时候,不能切换到动态加入的数据源上。
一个静态主库(MYSQL),多个动态项目库(SQLITE)
一个主库(MYSQL)的配置在yaml中,配置如下
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/kdss_new_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Abcd1234
driverClassName: com.mysql.cj.jdbc.Driver
多个项目库为手动方式,在请求项目数据时,动态添加当前项目的数据库(SQLITE),代码如下
@Autowired
private DataSource dataSource;
@Autowired
private DruidDataSourceCreator druidDataSourceCreator;
public Set<String> addDruid(@Validated @RequestBody DataSourceDTO dto) {
DataSourceProperty dataSourceProperty = new DataSourceProperty();
BeanUtils.copyProperties(dto, dataSourceProperty);
dataSourceProperty.setLazy(true);
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
DataSource dataSource = druidDataSourceCreator.createDataSource(dataSourceProperty);
ds.addDataSource(dto.getPoolName(), dataSource);
return ds.getDataSources().keySet();
}
在Service层对DS进行标识,代码如下
@Service
@DS("#project")
public class FileSpaceConfigServiceImpl extends BaseLogicServiceImpl<FileSpaceConfigMapper,FileSpaceConfig> implements IFileSpaceConfigService
{
/** 路径是否唯一的返回结果码 */
public final static String PATH_UNIQUE = "0";
public final static String PATH_NOT_UNIQUE = "1";
/**
* 根据条件查找数据
* @param fileSpaceConfig 条件
* @return 符合条件的数据
*/
@Override
public List<FileSpaceConfig> selectList(FileSpaceConfig fileSpaceConfig) {
fileSpaceConfig.setFlag(0);
return getBaseMapper().selectAll(fileSpaceConfig);
}
}
Controller层调用的时候,设置上下文的值为项目编号
ProjectDataSourceContextHolder.setDataSourceType(poolName);
//上下文环境定义如下:
public class ProjectDataSourceContextHolder
{
public static final Logger log = LoggerFactory.getLogger(ProjectDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String projectId)
{
log.info("切换到{}数据源", projectId);
CONTEXT_HOLDER.set(projectId);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType()
{
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType()
{
CONTEXT_HOLDER.remove();
}
}
重写DsProcessor,如下:
public class DsHeaderProcessor extends DsProcessor {
private static final String HEADER_PREFIX = "#project";
@Override
public boolean matches(String key) {
return key.equals(HEADER_PREFIX);
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
if(key.equals(HEADER_PREFIX)){
return ProjectDataSourceContextHolder.getDataSourceType();
}else{
return key;
}
}
}
定义配置的使用场景
@Configuration
public class CustomerDynamicDataSourceConfig {
@Bean
public DsProcessor dsProcessor() {
DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
DsSessionProcessor sessionProcessor = new DsSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
}
}
【以下回答由 GPT 生成】
思路: 1. 在调用Service方法之前,通过切面拦截器动态设置数据源。 2. 在切面拦截器中实现动态切换数据源的逻辑。
具体步骤如下:
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.baomidou.dynamic.datasource.annotation.DS)")
public void dataSourcePointCut() {
}
@Before("dataSourcePointCut() && @annotation(ds)")
public void switchDataSource(JoinPoint joinPoint, DS ds) {
String value = ds.value();
DynamicDataSourceContextHolder.setDataSource(value);
}
@After("dataSourcePointCut()")
public void restoreDataSource(JoinPoint joinPoint) {
DynamicDataSourceContextHolder.clearDataSource();
}
}
该切面拦截器会拦截带有@DS
注解的方法,并根据注解中的值动态切换数据源。
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSource(String dataSourceKey) {
CONTEXT_HOLDER.set(dataSourceKey);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
该类使用ThreadLocal
保存当前线程使用的数据源的标识,在切换数据源时设置数据源标识,在方法执行完成后清除数据源标识。
@Configuration
public class DataSourceConfig {
@Autowired
private List<DataSource> dataSources;
@Bean
public DataSourceRouter dataSourceRouter() {
DataSourceRouter router = new DataSourceRouter();
router.setTargetDataSources(dataSources.stream()
.collect(Collectors.toMap(DataSource::getName, Function.identity())));
router.setDefaultTargetDataSource(dataSources.get(0));
return router;
}
@Primary
@Bean(name = "dataSource")
public DynamicRoutingDataSource dynamicRoutingDataSource(DataSourceRouter dataSourceRouter) {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
dynamicRoutingDataSource.setDataSourceLookup(new MapDataSourceLookup(dataSourceRouter.getDataSources()));
dynamicRoutingDataSource.setDefaultDataSource(dataSourceRouter.getDefaultTargetDataSource());
return dynamicRoutingDataSource;
}
@Bean
public DataSourceAspect dataSourceAspect() {
return new DataSourceAspect();
}
@Bean
public DynamicDataSourceContextHolder dynamicDataSourceContextHolder() {
return new DynamicDataSourceContextHolder();
}
}
这里主要是配置了 DataSourceRouter
和 DynamicRoutingDataSource
,前者用于管理多个数据源,后者用于动态切换数据源。
@DS
注解,指定使用的数据源。@Service
public class FileSpaceConfigServiceImpl extends BaseLogicServiceImpl<FileSpaceConfigMapper, FileSpaceConfig> implements IFileSpaceConfigService {
@DS("#project")
public void doSomething() {
...
}
}
通过@DS("#project")
注解来指定使用的数据源,其中#project
可以是一个表达式,动态的从方法的参数、类属性等地方获取数据源的标识。
@Autowired
private IFileSpaceConfigService fileSpaceConfigService;
public void invokeServiceMethod(String project) {
DynamicDataSourceContextHolder.setDataSource(project);
fileSpaceConfigService.doSomething();
DynamicDataSourceContextHolder.clearDataSource();
}
在调用Service方法之前,使用DynamicDataSourceContextHolder.setDataSource(project)
来设置数据源的标识,在调用完成后使用DynamicDataSourceContextHolder.clearDataSource()
来清除数据源的标识。
这样就解决了mybatis-plus动态数据源切换的问题。
注:以上是一种思路和解决方案,具体实现可能需要根据项目的具体情况进行调整。