搭建一个完整的微服务系统(二):动态切换数据库

        上节末,我们提到了用动态切换数据库的方式来实现SaaS,这一节我来告诉大家具体怎么做。在文章最后,会有一个完整示例代码的链接,文章中,我只会说明有哪些代码在使用中需要处理。
        这个例子本身和国际汇款没有任何关系,只是为了让大家更容易理解和使用这个例子。

实现方式

        动态切换数据库的方法很多,我给大家介绍的是通过AOP来切换数据源这种模式,这种模式最是灵活,即使想在一个业务方法里操作多个数据库都没有问题。

step1. 数据源配置

        比如做一个世界排名前100的大学介绍,我们创建两个数据库,一个用来存中文信息,一个用来存英文信息(两个数据库中的表[school_info]结构一样,只有3个字段:id,name,resume),如下:

hikari:
  cnDb:
    jdbc-url: jdbc:mysql://127.0.0.1:3306/study_cn?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=CTT
    username: root
    password: root
  enDb:
    jdbc-url: jdbc:mysql://127.0.0.1:3306/study_en?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=CTT
    username: root
    password: root

step2. 定义多个数据源

@Component
@Data
@ConfigurationProperties(prefix = "hikari")
public class DBProperties {
    private HikariDataSource cnDb;
    private HikariDataSource enDb;
}

step3. 声明数据源(注意"cnDb"和"enDb"要和yml文件一致)

@Bean(name = "dataSource")
public DataSource dataSource() {
    //按照目标数据源名称和目标数据源对象的映射存放在Map中
    Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
    targetDataSources.put("cnDb", this.properties.getCnDb());
    targetDataSources.put("enDb", this.properties.getEnDb());
    //采用是想AbstractRoutingDataSource的对象包装多数据源
    DynamicDataSource dataSource = new DynamicDataSource();
    dataSource.setTargetDataSources(targetDataSources);
    //设置默认的数据源,当拿不到数据源时,使用此配置
    dataSource.setDefaultTargetDataSource(this.properties.getCommon());
    return dataSource;
}

step4. 声明一个aop

@Component
@Aspect
@Slf4j
@Order(1)
public class DataSourceAspect {
    //指定AOP切入点
    @Pointcut("execution(* com.example.multiDataSource.service..*.*(..))")
    public void service() {
    }

    @Before("service()")
    public void before(JoinPoint point) {
        try {
            String whodb = ThreadLocalHolder.getWhodb();

            DynamicDataSourceHolder.putDataSource(whodb);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    //执行完切面后,将线程共享中的数据源名称清空
    @After("service()")
    public void after(JoinPoint joinPoint){
        DynamicDataSourceHolder.removeDataSource();
    }
}

        上面这段代码有三个地方需要注意:
            1. 注解@Order(1)的作用:因为我们一般会在service实现类加上注解@Transactional,而这个注解本身也是aop,这就需要指定执行顺序,当然咱们写的切换数据库的注解优先级要高于事务注解@Transactional的。
            2. aop的切入点,放在哪一层都可以,这个没有要求,你只需要把它改成你项目里对应的包名就可以了。
            3. String whodb = ThreadLocalHolder.getWhodb(); 这句代码是用来获取应该切换到哪个数据库的,你当然可以从方法的参数中获取,但那意味着你每个方法都要加上这个参数。在本例子中,我要求客户端请求时,在header里加上参数:language(zh-中文,en-英文),然后把它放到ThreadLocalHolder里,这里取出来就可以了。

总结:只需要这4步,你的系统就能实现数据库动态切换了。这个功能的应用场景很广,不光是我们提到过的SaaS,多语言切换,其它如分库分表等,都是经常会使用到的。

示例代码

搭建一个完整的微服务系统(一):系统设计

猜你喜欢

转载自blog.csdn.net/yuyryd/article/details/107001314