上节末,我们提到了用动态切换数据库的方式来实现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,多语言切换,其它如分库分表等,都是经常会使用到的。