笔者公司目前数据库进行了分库操作,分成了几个库,存放用户相关信息的user库,存放资产信息的asset库,存放直播相关信息的live库。在平时开发过程中涉及多库操作的地方,需要通过rpc调用对应服务。这样的开发模型大体上没问题,因为既然拆分库了,应用跟随数据库进行拆分,也能防止以后开发代码混乱。但是这样缺少了灵活性,所以我这里为我们项目增添多数据源功能。
调研调研了两种spring+mybatis数据源方案。
1 多份配置在项目中,为每个库分别设置一套数据源配置。
DataSource,SqlSessionFactory,MapperScannerConfigurer分别对应数据源配置多份,
这里要注意的是mapper.xml文件地址也需要根据数据源来进行分包存放。由于我们项目xml文件过多,迁移麻烦,同时也感觉这种方式不灵活,所以并没有使用此种方式。
这是spring提供的多数据源方式,配合注解和aop可以灵活进行数据源的切换,非常好用。下面重点进行这种方式的介绍。
2.1 配置枚举对应不同数据源设置美剧
public static enum DataSourceEnum {
USER, ASSET
}
2.2 DataSource ThreadLocal
将数据源枚举放进线程本地缓存,方便后续切换。
public static class DataSourceTypeHolder {
private static final ThreadLocal dataSourceTypes = ThreadLocal.withInitial(() -> DataSourceEnum.USER);
public static DataSourceEnum get() {
return dataSourceTypes.get();
}
public static void set(DataSourceEnum dataSourceType) {
dataSourceTypes.set(dataSourceType);
}
}
2.3 配置多数据源
如下代码所示,分别对应两个库设置两个数据源。
@Bean
public DataSource user() {
MysqlDataSource source = new MysqlDataSource();
source.setURL("jdbc:mysql://localhost:3306/user");
source.setUser("root");
source.setPassword("123456");
return source;
}
@Bean
public DataSource asset() {
MysqlDataSource source = new MysqlDataSource();
source.setURL("jdbc:mysql://localhost:3306/asset");
source.setUser("root");
source.setPassword("123456");
return source;
}
2.4 配置DynamicDataSource
自定义多数据源继承 AbstractRoutingDataSource, 将多数据源设置到动态数据源,方便后续进行切换,并交由spring管理。
public static class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}
@Bean
public DynamicDataSource dynamicDataSource() {
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setDefaultTargetDataSource(master());
HashMap
2.5 其他mybatis配置
@Bean
public SqlSessionFactoryBean sqlSessionFactory() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(this.dynamicDataSource());
bean.setMapperLocations(new ClassPathResource("io/piper/server/spring/pojo/mapper/xml/UserMapper.xml"));
return bean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer config = new MapperScannerConfigurer();
config.setbasePackage("io/piper/server/spring/pojo/mapper");
return config;
}
@Bean
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(user());
return manager;
}
2.6 业务代码示例
如下,执行数据库操作之前,首先通过ThreadLocal设置将要使用的数据源。
public String find() {
Config.DataSourceTypeHolder.set(Config.DataSourceEnum.ASSET);
return JSON.toJSONString(userMapper.selectByExample(null), true);
}
2.7 注解切换数据源
以上业务已经能够满足我们业务数据源切换需求,不过如果能结合注解进行切换岂不是更好,那样就不用手动操作了。
如下定义注解,并配置aop拦截方法上边有自定义注解的方法,解析要使用的数据源,进行动态切换。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface Routing {
DataSourceEnum value() default DataSourceEnum.USER;
}
@Component
@org.aspectj.lang.annotation.Aspect
public static class Aspect {
@Around("@annotation(io.piper.datasource.Config.Routing)")
public void before(ProceedingJoinPoint jp) {
MethodSignature signature = (MethodSignature) jp.getSignature();
Routing routing = signature.getMethod().getAnnotation(Routing.class);
try {
DataSourceTypeHolder.set(routing.value());
jp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
最终,我们的业务代码如下,是不是就简单多了呢?
@Config.Routing(Config.DataSourceEnum.ASSET)
public String find() {
return JSON.toJSONString(userMapper.selectByExample(null), true);
}
结语
以上代码已经上传到 我的码云 点击进去找 routing-data-source
另外推荐下我的开源作品 PiperChat
PiperChat 是一款简洁高效的即时通讯服务,提供多种技术供开发者选择,帮助开发者快速构建高并发的即时通讯服务。



