【java】多数据源案例解决方案

【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;
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

请叫我张小明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值