Mybatis每次操作数据库时都会先获取当前数据源,spring-jdbc模块提AbstractRoutingDataSource让我们指定数据源,因此我们可以动态的指定数据源。
一、设置数据源,spring-jdbc模块提AbstractRoutingDataSource让我们指定数据源,我们实现其determineCurrentLookupKey()方法返回一个我们需要的数据源即可;
/**
* 动态数据源类
*
* @author hmh
*/
public class DynamicDatasource extends AbstractRoutingDataSource {
/**
* 这里的determineCurrentLookupKey方法,需要返回一个数据源,也就是说返回一个数据源的映射,
* 这里返回一个DynamicDatasourceHolder.getDataSource()方法的返回值,DynamicDatasourceHolder是一个保存多个数据源的地方
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDatasourceHolder.getDataSource();
}
}
二、创建DynamicDatasourceHolder工具类,这里我使用的是ThreadLocal。
/**
* 多数据源工具类、使用ThreadLocal保证一个线程使用一个数据源
*
* @author hmh
*/
public class DynamicDatasourceHolder {
private static final ThreadLocal<String> LOCAL = new ThreadLocal<>();
public static String getDataSource() {
return LOCAL.get();
}
public static void setDataSource(String dataSourceKey) {
LOCAL.set(dataSourceKey);
}
public static void removeDataSource() {
LOCAL.remove();
}
}
三、注册多数据源(将配置文件中配置的多数据源注册到DynamicDataSource中)
1,获取多数据源配置类
/**
* 多数据源配置
*
* @author hmh
*/
@Configuration
@ConfigurationProperties(prefix = "spring.dynamic")
@Data
public class DynamicDataSourceProperties {
/**
* 多数据源
*/
private List<Map<String, DataSourceConfig>> datasource;
/**
* 默认数据源(不默认数据源且找不到指定数据源时报错)
*/
private String defaultDatasource;
@Data
public static class DataSourceConfig {
private String jdbcUrl;
private String username;
private String password;
private String driverClassName;
}
}
2,注册多数据源(配置文件中配置多少个数据源就注册多少数据源,注册数据源的名称为配置文件中数据源的key,与网上大多数写死数据源不相同,本文章出发点为"公共")
/**
* 多数据源配置类
*
* @author hmh
*/
@Configuration
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
@RequiredArgsConstructor
public class DynamicDatasourceConfiguration {
private final DynamicDataSourceProperties properties;
@Bean
@Primary
public DataSource dataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(16);
List<Map<String, DynamicDataSourceProperties.DataSourceConfig>> datasource = properties.getDatasource();
String defaultDatasource = properties.getDefaultDatasource();
if (Func.isEmpty(datasource)) {
throw new ServiceException("请配置多数据源");
}
DynamicDatasource dynamicDatasource = new DynamicDatasource();
datasource.forEach(item -> item.forEach((datasourceName, dataSource) -> {
DataSourceBuilder<?> builder = DataSourceBuilder.create();
builder.url(dataSource.getJdbcUrl());
builder.username(dataSource.getUsername());
builder.password(dataSource.getPassword());
builder.driverClassName(dataSource.getDriverClassName());
DataSource build = builder.build();
dataSourceMap.put(datasourceName, build);
if (Func.isNotBlank(defaultDatasource) && Func.equals(defaultDatasource, datasourceName)) {
dynamicDatasource.setDefaultTargetDataSource(build);
}
}));
dynamicDatasource.setTargetDataSources(dataSourceMap);
return dynamicDatasource;
}
}
四、定义注解以及切面,实现根据注解配置切换数据源
1,定义注解
/**
* 动态数据源的注解
* 用在类和方法上,方法上的优先级大于类上的
* 默认值是master
*
* @author hmh
* @date 2023/12/6 16:19
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DDS {
String value() default CommonConstant.MASTER;
}
2,注解切面(使用ThreadLocal注意在方法执行完成后清空ThreadLocal避免内存溢出)
/**
* 动态数据源切面
*
* @author hmh
*/
@Aspect
@Component
@Order(value = Integer.MIN_VALUE)
public class DynamicDatasourceAspect {
/**
* 切点,切的是带有@DDS注解的方法或类
*/
@Pointcut("@annotation(com.assets.dynamic.annotation.DDS) || @within(com.assets.dynamic.annotation.DDS)")
public void pointcut() {
}
/**
* 环绕通知
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String datasourceKey;
// 方法上的注解
DDS annotationMethod = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(DDS.class);
if (Func.notNull(annotationMethod)) {
datasourceKey = annotationMethod.value();
} else {
// 类上的注解
datasourceKey = joinPoint.getTarget().getClass().getAnnotation(DDS.class).value();
}
// 设置数据源
DynamicDatasourceHolder.setDataSource(datasourceKey);
try {
return joinPoint.proceed();
} finally {
DynamicDatasourceHolder.removeDataSource();
}
}
}
五、配置示例
spring:
# 多数据源配置
dynamic:
# 默认的数据源
default-datasource: master
datasource:
# 数据源名称
- master:
jdbc-url: jdbc:mysql://localhost:3306/XXX?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
- slave:
jdbc-url: jdbc:mysql://localhost:3306/XXX1?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver