【java】同内容多数据源案例解决方案
问题背景
公司为了规范信息管理,想要统一处理两个子公司员工的数据。这些信息原本分别由子公司各自管理,分别在不同的数据库,但是信息遵循了相同的规则。那么如何才能多个数据库共用一套业务代码,简化开发呢?
思路
【核心】在于解决相同结构不同内容的数据保存在多个数据库如何切换的问题。
我们可以设置多数据库源,通过代码动态选择目标数据源。
首先,在请求接口时可以通过向请求头绑定参数的形式指定目标数据库。
然后,在需要动态指定默认数据库的接口加注解标识。
最后,配置Aop扫描该注解,获取请求头中的数据源参数,设置目标数据库。
技术方案
技术:Aop+ThreadLocal+AbstractRoutingDataSource
Aop:面向切面编程,用于统一获取请求头中的数据源参数。
ThreadLocal:本地线程变量,由于存放当次请求的数据源。
AbstractRoutingDataSource:多数据源接口类,通过继承该类重写determineCurrentLookupKey()方法来切换目标数据源
案例【项目代码】
目录指引
流程及核心代码解析
1.向服务器发送指定目标数据源的请求
2.接口收到带有指定目标数据源的请求
EmployeeController类
描述:员工信息请求接口
@CustomDynamicDbChange 标注了该类是一个需要动态切换数据源的接口,以便后面Aop通过动态扫描识别该接口并在执行前读取请求头中数据源参数。
@RestController
@RequestMapping("/emp")
public class EmployeeController {
@Autowired
private EmployeeService employeeServices;
@CustomDynamicDbChange //此处用于标记该接口需要动态切换数据源
@GetMapping
public R all(){
List<Employee> list = employeeServices.list();
return R.success(list);
}
}
3.Aop解析数据源参数信息
CustomDynamicDbchangeAspect类
描述:Aop配置类
Aop通过动态扫描识别接口,并在绑定的方法执行前读取请求头中数据源参数。
并将从请求头中获取的数据配置到CustomDynamicDataSourceContextHolder类的本地线程变量。
@Slf4j
@Aspect
@Component
public class CustomDynamicDbchangeAspect {
@Before("@annotation(com.xiaoming.dds.annotation.CustomDynamicDbChange)")
public void doBefore(JoinPoint joinPoint){
//获取请求属性
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取请求对象
HttpServletRequest request = requestAttributes.getRequest();
//获取请求头中数据源编码
String dbCode = request.getHeader("dynamicDbCode");
if (!StringUtils.hasLength(dbCode)) {
throw new RuntimeException("请求header中数据源编码为空");
}
if (!CustomDynamicDataSourceContextHolder.containsDatasource(dbCode)) {
log.info("请求header中数据源编码:『{}』,不在范围内。",dbCode);
throw new RuntimeException("请求header中数据源编码不在范围内");
}
//设置数据源编码
CustomDynamicDataSourceContextHolder.setDataSourceType(dbCode);
}
@After("@annotation(com.xiaoming.dds.annotation.CustomDynamicDbChange)")
public void doAfter(JoinPoint joinPoint){
CustomDynamicDataSourceContextHolder.clearDataSourceType();
}
}
4.切换数据源
CustomDynamicDatasource类
描述:切换数据源
实现AbstractRoutingDataSource接口重写determineCurrentLookupKey方法来设置切换数据源的逻辑。
public class CustomDynamicDatasource extends AbstractRoutingDataSource {
Map<Object, Object> dataSourceMap = null;
@Override
protected Object determineCurrentLookupKey() {
// 动态配置数据源
if (StringUtils.hasLength(CustomDynamicDataSourceContextHolder.getDataSourceType())) {
log.info("动态数据源编码为: {}", CustomDynamicDataSourceContextHolder.getDataSourceType());
} else {
log.info("使用默认数据源");
}
return CustomDynamicDataSourceContextHolder.getDataSourceType();
}
}
DatabaseConfig类
描述:配置数据源
使用@primary配置优先加载dynamicDataSource【动态数据源:实际上返回的是继承了AbstractRoutingDataSource接口的实现类】,并配置了多数据源及加载方式。
@Configuration
public class DatabaseConfig {
@Autowired
private DataSourceDB2Property dataSourceDB2Property;
@Autowired
private DataSourceDB1Property dataSourceDB1Property;
/**
* 子公司1员工数据库
* @return
*/
@Bean(name = "DataSource1")
@Qualifier("DataSource1")
public DataSource dataSource1(){
return DataSourceBuilder.create()
.driverClassName(dataSourceDB1Property.getDriverClassName())
.url(dataSourceDB1Property.getUrl())
.username(dataSourceDB1Property.getUsername())
.password(dataSourceDB1Property.getPassword())
.build();
}
/**
* 子公司2员工数据库
* @return
*/
@Bean(name = "DataSource2")
@Qualifier("DataSource2")
public DataSource dataSource2(){
return DataSourceBuilder.create()
.driverClassName(dataSourceDB2Property.getDriverClassName())
.url(dataSourceDB2Property.getUrl())
.username(dataSourceDB2Property.getUsername())
.password(dataSourceDB2Property.getPassword())
.build();
}
@Bean(name = "dynamicDataSource")
@Primary //配置优先生效
public DataSource dynamicDataSource() {
CustomDynamicDatasource datasource = new CustomDynamicDatasource();
//设置数据源
datasource.dataSourceMap = new HashMap<>();
datasource.dataSourceMap.put(DynamicDbEnum.DB1.getDbCode(),dataSource1());
datasource.dataSourceMap.put(DynamicDbEnum.DB2.getDbCode(),dataSource2());
//存放所有数据源
datasource.setTargetDataSources(datasource.dataSourceMap);
//设置默认数据源
datasource.setDefaultTargetDataSource(dataSource1());
//存储数据源编码
CustomDynamicDataSourceContextHolder.dataSourceList.addAll(datasource.dataSourceMap.keySet());
return datasource;
}
}