这不是一个新的知识点扩展,顶多算是,Spring的AOP特性的一个应用。
那么下面开始今天的学习之旅!

场景

数据库读写分离,或者分库,总之多数据源的场景,怎么样实现自动切换(PS:不考虑各种分库分表的代理中间件噢)

使用

结合场景,那么我们的目的很简单。
就是利用Spring的AOP特性,创建一个注解类修饰service 方法,通过注解切入,设置数据库来源,完成调用后,再恢复原数据库来源。

那么我们需要怎么做

  • 注解类
  • AOP切面类,处理此注解的
  • 设置数据库来源的类

上面我们只是做到了,设置,查数据库之前,切换数据库来源。

  • 数据源信息在哪一步读取
  • 是怎么实现把切换后的数据源信息告诉dao层的

如果我们解决了以上问题,那么这个功能就应该可以轻松的实现咯。 这里就要提这次的重头戏

当在业务层需要涉及到查询多种同数据库的场景下,我们通常需要在执行sql的时候动态指定对应的datasource。 而Spring的AbstractRoutingDataSource则正好为我们提供了这一功能点。

先上例子吧,看完例子回后,我们再尝试着分析AbstractRoutingDataSource的来龙去脉看看能不能解答上面的2个疑点。

实例参考 这是别人的博文,这里我就不赘叙咯,免得文章拖沓,没有实质的意义。

这里仅仅贴我的核心类

public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);

    private static String dynamicPrefix = "spring.core.datasource";

    private static String filters = "Filters";

    private static String connectionProperties = "ConnectionProperties";
@Override
protected Object determineCurrentLookupKey() {
log.debug("数据源为{}", DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
} /**
* 构建动态多数据源
* @param route
* @param prefix
* @return
*/
public static DynamicDataSource doCreateDataSource(String route, String prefix){
if(StringUtils.isEmpty(route)){
throw new IllegalArgumentException("create route dbSource is error; route is null");
}
DynamicDataSource dynamicDataSource = new DynamicDataSource();
String[] dataNames = route.split(",");
if(null == dataNames || dataNames.length == 0){
throw new IllegalArgumentException("create route dbSource is error; Route is format error, please refer to dataSource1,dataSource2");
}
Map<Object, Object> dsMap = new HashMap(10);
//遍历数据源
String defaultDataName = null;
for(String dataName : dataNames){
if(StringUtils.isEmpty(defaultDataName)){
defaultDataName = dataName;
}
//得到当前数据源配置
Map<String, Object> paramMap = getDataSourceProperties(prefix, dataName);
if(CollectionUtils.isEmpty(paramMap)){
throw new CloudException("init database is error, param empty");
}
DruidDataSource druidDataSource = (DruidDataSource) DataSourceBuilder.create().type(DruidDataSource.class).build();
//将配置参数,注入druidDataSource对象
new RelaxedDataBinder(druidDataSource).bind(new MutablePropertyValues(paramMap));
//设置数据源名称
druidDataSource.setName(dataName);
try {
//初始化数据源
druidDataSource.init();
} catch (SQLException e) {
log.error("init database {} is error " + e.getMessage(), dataName, e);
throw new CloudException("init" +dataName +"db is error");
}
dsMap.put(dataName, druidDataSource);
}
//首个数据源为默认
dynamicDataSource.setDefaultTargetDataSource(dsMap.get(defaultDataName));
dynamicDataSource.setTargetDataSources(dsMap); return dynamicDataSource;
} /**
* 根据系统参数获取数据源配置
* @param prefix
* @param dataName
* @return
*/
private static Map<String, Object> getDataSourceProperties(String prefix, String dataName){
Map<String, Object> paramMap = new HashMap<>();
//
Properties properties = GlobalRuntimeConfigFactory.getInstance().getProperties();
//prefix=spring.datasource 共有配置获取
doIterator(properties, prefix, paramMap);
prefix = dynamicPrefix + "." + dataName; doIterator(properties, prefix, paramMap);
return paramMap;
} /**
* 遍历配置对象launchArgs,将需要配置存入paramMap
* @param properties 配置对象
* @param prefix 需要的配置前缀
* @param paramMap 返回需要的配置
*/
private static void doIterator(Properties properties, String prefix, Map<String, Object> paramMap){
if(null == properties ){
return;
}
//launchArg格式 --key=value
for(Map.Entry<Object,Object> entry: properties.entrySet()){
if(StringUtils.isEmpty(entry)){
continue;
}
String keyName = entry.getKey().toString();
String value = entry.getValue().toString();
if (!StringUtils.isEmpty(keyName) && keyName.startsWith(prefix)) {
String key = StringUtils.toUpperCaseFirstOne(keyName.substring(prefix.length() + 1));
if (!key.contains(".")) {
paramMap.put(StringUtils.toUpperCaseFirstOne(keyName.substring(prefix.length() + 1)), value);
}
}
}
}
}

@Value("${spring.datasource.route:}")
private String route; public static final String PREFIX = "spring.datasource"; @Bean
@Primary
@ConfigurationProperties(prefix = DataSourceConfiguration.PREFIX)
@ConditionalOnMissingClass("spring.framework.jta.configuration.PrimaryXADataSourceConfiguration")
public DataSource dataSource() {
if(StringUtils.isEmpty(route)){
return DataSourceBuilder.create().type(DruidDataSource.class).build();
// return DataSourceBuilder.create().type(SweetDataSource.class).build();
} else {
//启动动态多数据源
return DynamicDataSource.doCreateDataSource(route, PREFIX);
}
}

原理

原理,不是我总计的,还是上面那个文章的博主的原话吧

AbstractRoutingDataSource 动态路由数据源的注入原理,可以看到这个内部类里面包含了多种用于做数据源映射的map数据结构

  • private Map<Object, DataSource> resolvedDataSources;

也就是上边我们所提及的使用于查询当前数据源key的方法

  • protected abstract Object determineCurrentLookupKey();

而在该类的afterPropertiesSet里面,又有对于初始化数据源的注入操作,这里面的targetDataSources 正是上文中我们对在初始化数据源时候注入的信息

@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}

最新文章

  1. 一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版
  2. kettle定时任务_第三方合作方有订单自动发送邮件通知_20161214
  3. windows下nginx的启动关闭
  4. ListView具有多种item布局——实现微信对话列
  5. TI CC2541的串口输出.
  6. android开发 drawtext的开始坐标位置
  7. 01-04-03【Nhibernate (版本3.3.1.4000) 出入江湖】Criteria API关联查询
  8. 如何获取本机IP
  9. Jquery基础知识01
  10. ajax设置默认值ajaxSetup()方法
  11. 对mysql数据库中字段为空的处理
  12. 【SQL 代码】SQL 语句记录(不定时更新)
  13. centos7环境开启WIFI热点
  14. Hadoop Yarn环境配置
  15. 转)VCSA 6.5重启无法访问,报错“503 Service Unavailable”的解决方法
  16. socket心跳超时检测,快速处理新思路(适用于超大量TCP连接情况下)
  17. 【ContestHunter】【弱省胡策】【Round8】
  18. (转)Inno Setup入门(十三)——Pascal脚本(2)
  19. vertex shader(1)
  20. 【洛谷P1118】数字三角形

热门文章

  1. interface和abstract 的区别和相同点
  2. LAMP搭建wordpress
  3. Django之内置分页器(paginator)
  4. AspectJ JoinPoint及ProceedingJoinPoint 简要api文档
  5. Codeblocks运行按钮变灰,卡程序编译
  6. [JavaWeb基础] 021.Action中result的各种转发类型
  7. JVM系列.JDK演进历史
  8. parrot os的一些坑
  9. jchdl进展 - 20180918
  10. Shell脚本 (二) 变量与运算符