引言

其实对于分库分表这块的场景,目前市场上有很多成熟的开源中间件,eg:MyCAT,Cobar,sharding-JDBC等。

本文主要是介绍基于springboot的多数据源切换,轻量级的一种集成方案,对于小型的应用可以采用这种方案,我之前在项目中用到是因为简单,便于扩展以及优化。

应用场景

假设目前我们有以下几种数据访问的场景: 
1.一个业务逻辑中对不同的库进行数据的操作(可能你们系统不存在这种场景,目前都时微服务的架构,每个微服务基本上是对应一个数据库比较多,其他的需要通过服务来方案。), 
2.访问分库分表的场景;后面的文章会单独介绍下分库分表的集成。

假设这里,我们以6个库,每个库1000张表的例子来介绍),因为随着业务量的增长,一个库很难抗住这么大的访问量。比如说订单表,我们可以根据userid进行分库分表。 
分库策略:userId%6[表的数量]; 
分库分表策略:库路由[userId/(6*1000)/1000],表路由[ userId/(6*1000)%1000].

集成方案

方案总览: 
方案是基于springjdbc中提供的api实现,看下下面两段代码,是我们的切入点,我们都是围绕着这2个核心方法进行集成的。

第一段代码是注册逻辑,会将defaultTargetDataSource和targetDataSources这两个数据源对象注册到resolvedDataSources中。

第二段是具体切换逻辑: 如果数据源为空,那么他就会找我们的默认数据源defaultTargetDataSource,如果设置了数据源,那么他就去读这个值 Object lookupKey = determineCurrentLookupKey();我们后面会重写这个方法。

我们会在配置文件中配置主数据源(默认数据源)和其他数据源,然后在应用启动的时候注册到spring容器中,分别给defaultTargetDataSource和targetDataSources进行赋值,进而进行Bean注册。

真正的使用过程中,我们定义注解,通过切面,定位到当前线程是由访问哪个数据源(维护着一个ThreadLocal的对象),进而调用determineCurrentLookupKey方法,进行数据源的切换。

 1 public void afterPropertiesSet() {
 2         if (this.targetDataSources == null) {
 3             throw new IllegalArgumentException("Property 'targetDataSources' is required");
 4         }
 5         this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
 6         for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
 7             Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
 8             DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
 9             this.resolvedDataSources.put(lookupKey, dataSource);
10         }
11         if (this.defaultTargetDataSource != null) {
12             this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
13         }
14     }
15
16 @Override
17 public Connection getConnection() throws SQLException {
18         return determineTargetDataSource().getConnection();
19 }
20
21 protected DataSource determineTargetDataSource() {
22         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
23         Object lookupKey = determineCurrentLookupKey();
24         DataSource dataSource = this.resolvedDataSources.get(lookupKey);
25         if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
26             dataSource = this.resolvedDefaultDataSource;
27         }
28         if (dataSource == null) {
29             throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
30         }
31         return dataSource;
32     }

1.动态数据源注册 注册默认数据源以及其他额外的数据源; 这里只是复制了核心的几个方法,具体的大家下载git看代码。

 1  // 创建DynamicDataSource
 2         GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
 3         beanDefinition.setBeanClass(DyncRouteDataSource.class);
 4         beanDefinition.setSynthetic(true);
 5         MutablePropertyValues mpv = beanDefinition.getPropertyValues();
 6         //默认数据源
 7         mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
 8         //其他数据源
 9         mpv.addPropertyValue("targetDataSources", targetDataSources);
10         //注册到spring bean容器中
11         registry.registerBeanDefinition("dataSource", beanDefinition);

2.在Application中加载动态数据源,这样spring容器启动的时候就会将数据源加载到内存中。

1 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MybatisAutoConfiguration.class })
2 @Import(DyncDataSourceRegister.class)
3 @ServletComponentScan
4 @ComponentScan(basePackages = { "com.happy.springboot.api","com.happy.springboot.service"})
5 public class Application {
6     public static void main(String[] args) {
7         SpringApplication.run(Application.class, args);
8     }
9 }

3.数据源切换核心逻辑:创建一个数据源切换的注解,并且基于该注解实现切面逻辑,这里我们通过threadLocal来实现线程间的数据源切换;

 1 import java.lang.annotation.Documented;
 2 import java.lang.annotation.ElementType;
 3 import java.lang.annotation.Retention;
 4 import java.lang.annotation.RetentionPolicy;
 5 import java.lang.annotation.Target;
 6
 7
 8 @Target({ ElementType.TYPE, ElementType.METHOD })
 9 @Retention(RetentionPolicy.RUNTIME)
10 @Documented
11 public @interface TargetDataSource {
12     String value();
13     // 是否分库
14     boolean isSharding() default false;
15     // 获取分库策略
16     String strategy() default "";
17 }
18
19 // 动态数据源上下文
20 public class DyncDataSourceContextHolder {
21
22     private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
23
24     public static List<String> dataSourceNames = new ArrayList<String>();
25
26     public static void setDataSource(String dataSourceName) {
27         contextHolder.set(dataSourceName);
28     }
29
30     public static String getDataSource() {
31         return contextHolder.get();
32     }
33
34     public static void clearDataSource() {
35         contextHolder.remove();
36     }
37
38     public static boolean containsDataSource(String dataSourceName) {
39         return dataSourceNames.contains(dataSourceName);
40     }
41 }
42
43 /**
44  *
45  * @author jasoHsu
46  * 动态数据源在切换,将数据源设置到ThreadLocal对象中
47  */
48 @Component
49 @Order(-10)
50 @Aspect
51 public class DyncDataSourceInterceptor {
52
53     @Before("@annotation(targetDataSource)")
54     public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Exception {
55         String dbIndx = null;
56
57         String targetDataSourceName = targetDataSource.value() + (dbIndx == null ? "" : dbIndx);
58         if (DyncDataSourceContextHolder.containsDataSource(targetDataSourceName)) {
59             DyncDataSourceContextHolder.setDataSource(targetDataSourceName);
60         }
61     }
62
63     @After("@annotation(targetDataSource)")
64     public void resetDataSource(JoinPoint point, TargetDataSource targetDataSource) {
65         DyncDataSourceContextHolder.clearDataSource();
66     }
67 }
68
69 //
70 public class DyncRouteDataSource extends AbstractRoutingDataSource {
71 @Override
72     protected Object determineCurrentLookupKey() {
73         return DyncDataSourceContextHolder.getDataSource();
74     }
75     public DataSource findTargetDataSource() {
76         return this.determineTargetDataSource();
77     }
78 }

4.与mybatis集成,其实这一步与前面的基本一致。 
只是将在sessionFactory以及数据管理器中,将动态数据源注册进去【dynamicRouteDataSource】,而非之前的静态数据源【getMasterDataSource()】

 1     @Resource
 2     DyncRouteDataSource dynamicRouteDataSource;
 3
 4     @Bean(name = "sqlSessionFactory")
 5     public SqlSessionFactory getinvestListingSqlSessionFactory() throws Exception {
 6         String configLocation = "classpath:/conf/mybatis/configuration.xml";
 7         String mapperLocation = "classpath*:/com/happy/springboot/service/dao/*/*Mapper.xml";
 8         SqlSessionFactory sqlSessionFactory = createDefaultSqlSessionFactory(dynamicRouteDataSource, configLocation,
 9                 mapperLocation);
10
11         return sqlSessionFactory;
12     }
13
14
15     @Bean(name = "txManager")
16     public PlatformTransactionManager txManager() {
17         return new DataSourceTransactionManager(dynamicRouteDataSource);
18     }

5.核心配置参数:

 1 spring:
 2   dataSource:
 3     happyboot:
 4       driverClassName: com.mysql.jdbc.Driver
 5       url: jdbc:mysql://localhost:3306/happy_springboot?characterEncoding=utf8&useSSL=true
 6       username: root
 7       password: admin
 8     extdsnames: happyboot01,happyboot02
 9     happyboot01:
10       driverClassName: com.mysql.jdbc.Driver
11       url: jdbc:mysql://localhost:3306/happyboot01?characterEncoding=utf8&useSSL=true
12       username: root
13       password: admin
14     happyboot02:
15       driverClassName: com.mysql.jdbc.Driver
16       url: jdbc:mysql://localhost:3306/happyboot02?characterEncoding=utf8&useSSL=true
17       username: root
18       password: admin    

6.应用代码

 1 @Service
 2 public class UserExtServiceImpl implements IUserExtService {
 3     @Autowired
 4     private UserInfoMapper userInfoMapper;
 5     @TargetDataSource(value="happyboot01")
 6     @Override
 7     public UserInfo getUserBy(Long id) {
 8         return userInfoMapper.selectByPrimaryKey(id);
 9     }
10 }

本文转载自:https://blog.csdn.net/xuxian6823091/article/details/81051515

转载于:https://www.cnblogs.com/xiyunjava/p/9317625.html

关于SpringBoot中的多数据源集成相关推荐

  1. springboot 中动态切换数据源(多数据源应用设计)

    目录 原理图 数据库 项目结构 启动类 entity controller service mapper 配置文件 线程上下文 (DataSourceHolder) 动态数据源 DynamicData ...

  2. springboot中配置多数据源mybatisPlus

    文章目录 1 背景 2 版本 3 配置 3.1 pom配置 3.1.1 父类pom 3.1.2 pom依赖 3.1.3 插件 3.2 application.yml配置 3.3 启动类配置 4 使用 ...

  3. springboot中配置mybatis数据源,使用阿里的 Druid 数据库连接池

    参考了很多文章,记录下自己的学习过程! 参考:https://blog.csdn.net/weixin_40776321/article/details/99633110 1. 在pom.xml中添加 ...

  4. SpringBoot中多数据源的配置

    1.场景还原 在实际项目中,一个工程配置多个数据源很常见,工程可能会根据业务或者模块访问不同的数据库或表:今天笔者就springboot中配置多数据源作个详细的讲解 2.实现方案 注意:一个应用工程中 ...

  5. 数据源(DataSource)是什么以及SpringBoot中数据源配置

    数据源 数据源,简单理解为数据源头,提供了应用程序所需要数据的位置.数据源保证了应用程序与目标数据之间交互的规范和协议,它可以是数据库,文件系统等等.其中数据源定义了位置信息,用户验证信息和交互时所需 ...

  6. springboot的jsp应该放在哪_在springboot中集成jsp开发

    springboot就是一个升级版的spring.它可以极大的简化xml配置文件,可以采用全注解形式开发,一个字就是很牛. 在springboot想要使用jsp开发,需要集成jsp,在springbo ...

  7. springboot中DataSource数据源实例产生时机及所需环境

    今天学习springboot中数据源配置时想到一些问题: 仅配置mysql的username.password.url时,springboot会默认使用连接池管理数据连接源吗? 为了解惑,直接在当前项 ...

  8. apache spark_如何将自定义数据源集成到Apache Spark中

    apache spark 如今,流数据是一个热门话题,而Apache Spark是出色的流框架. 在此博客文章中,我将向您展示如何将自定义数据源集成到Spark中. Spark Streaming使我 ...

  9. 如何将自定义数据源集成到Apache Spark中

    如今,流数据是一个热门话题,而Apache Spark是出色的流框架. 在此博客文章中,我将向您展示如何将自定义数据源集成到Spark中. Spark Streaming使我们能够从各种来源进行流传输 ...

最新文章

  1. 测试Robotium
  2. SAP事务码MM17物料主数据批量维护
  3. Python如何发布程序
  4. Serverless在编程教育中的实践
  5. [攻防世界 pwn]——time_formatter(内涵peak小知识)
  6. ip数据报首部校验和的计算
  7. 电脑主板接口_电脑主板接口大全
  8. python实现web服务器_python实现web服务器
  9. mysql 3.23.49,将旧的3.23.49 MySQL数据库转移到5.0.51 MySQL数据库 – 用ANSI和UTF-8编码...
  10. IOS根据经纬度算距离
  11. 安全教育思维导图模板分享
  12. Thinkpad预装win10硬盘分区
  13. 焊接知识与技能(嵌入式硬件篇)
  14. S7-1200PLC程序PN总线三路V90伺服轴控制实际应用项目
  15. java重命名_java实现文件重命名的方法
  16. Spring Boot (Filter)过滤器的实现以及使用场景
  17. 音频传输之Jitter Buffer设计与实现
  18. element ui 表格内容 合计
  19. 字符串转换成JSON
  20. 汇桔宝时代:重新定义互联网营销与流量格局!

热门文章

  1. java输出栈的弹出序列_剑指offer:栈的压入、弹出序列(Java)
  2. unityui等比例缩放_Unity 4.6-如何针对每种分辨率将GUI元素缩放到合适的大小
  3. 深度学习之自编码器(2)Fashion MNIST图片重建实战
  4. 【算法竞赛学习】金融风控之贷款违约预测-模型融合
  5. Java学习笔记_字符串/静态static
  6. c malloc 头文件_干货笔记 | C/C++笔试面试详细总结(二)
  7. 『操作系统』 进程的描述与控制 Part2 进程同步
  8. Excel 2016双击无法打开文件的解决办法
  9. 如何设置运行在Virtualbox内的Ubuntu虚拟机的静态ip地址
  10. [深度学习] 自然语言处理 --- ALBERT 介绍