说明
基于springboot+jdk8
使用DataSource+切面的方式实现数据库动态切换,不影响程序正常数据库的操作,在此不做过多介绍,直接代码走起, 深入的东西可交流
application.yml配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test1
username: root
password: 123456
hikari:
idle-timeout: 570000
max-lifetime: 570000
report:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test2
username: root
password: 123456
hikari:
idle-timeout: 570000
max-lifetime: 570000
代码实现
注解
通过注解的方式实现动态切换,也可以通过不同的包名进行拦截切换
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
@Data
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = ListUtils.newArrayList();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceId){
return dataSourceIds.contains(dataSourceId);
}
}
数据源配置注册
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = MapUtils.newHashMap();
@Override
public void setEnvironment(Environment environment) {
initDefaultDataSource(environment);
initCustomDataSources(environment);
}
private void initDefaultDataSource(Environment env) {
Iterable<ConfigurationPropertySource> configurationPropertySources = ConfigurationPropertySources.get(env);
Binder binder = new Binder(configurationPropertySources);
BindResult<Properties> bindResult = binder.bind("spring.datasource", Properties.class);
Properties properties = bindResult.get();
defaultDataSource = buildDataSource(properties);
}
private void initCustomDataSources(Environment env) {
Iterable<ConfigurationPropertySource> configurationPropertySources = ConfigurationPropertySources.get(env);
Binder binder = new Binder(configurationPropertySources);
BindResult<Properties> bindResult = binder.bind("report.datasource", Properties.class);
Properties properties = bindResult.get();
DataSource ds = buildDataSource(properties);
if (ObjectUtils.isNull(ds)) {
bindResult = binder.bind("spring.datasource", Properties.class);
properties = bindResult.get();
ds = buildDataSource(properties);
}
customDataSources.put("report", ds);
}
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Properties properties) {
Object type = properties.get("type");
if (type == null) {
type = DATASOURCE_TYPE_DEFAULT;
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = properties.get("driver-class-name").toString();
String url = properties.get("url").toString();
String username = properties.get("username").toString();
String password = properties.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
.username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = MapUtils.newHashMap();
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
}
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
}
}
通过切面的方式拦截,实现数据源动态切换
@Aspect
@Order(-10)
@Component
public class DynamicDataSourceAspect {
public static Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
String dsId = targetDataSource.value();
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
SystemLogger.wrap(logger).info("数据源[{}]不存在,使用默认数据源 > {}" + targetDataSource.value() + point.getSignature());
} else {
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
}
}
@After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
启动类上加此注解
@Import(DynamicDataSourceRegister.class)