java集成多种数据库_关于SpringBoot中的多数据源集成

引言

其实对于分库分表这块的场景,目前市场上有很多成熟的开源中间件,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 voidafterPropertiesSet() {2 if (this.targetDataSources == null) {3 throw new IllegalArgumentException("Property 'targetDataSources' is required");4 }5 this.resolvedDataSources = new HashMap(this.targetDataSources.size());6 for (Map.Entry 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 @Override17 public Connection getConnection() throwsSQLException {18 returndetermineTargetDataSource().getConnection();19 }20

21 protectedDataSource 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 returndataSource;32 }

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

1 //创建DynamicDataSource

2 GenericBeanDefinition beanDefinition = newGenericBeanDefinition();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 @ServletComponentScan4 @ComponentScan(basePackages = { "com.happy.springboot.api","com.happy.springboot.service"})5 public classApplication {6 public static voidmain(String[] args) {7 SpringApplication.run(Application.class, args);8 }9 }

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

1 importjava.lang.annotation.Documented;2 importjava.lang.annotation.ElementType;3 importjava.lang.annotation.Retention;4 importjava.lang.annotation.RetentionPolicy;5 importjava.lang.annotation.Target;6

7

8 @Target({ ElementType.TYPE, ElementType.METHOD })9 @Retention(RetentionPolicy.RUNTIME)10 @Documented11 public @interfaceTargetDataSource {12 String value();13 //是否分库

14 boolean isSharding() default false;15 //获取分库策略

16 String strategy() default "";17 }18

19 //动态数据源上下文

20 public classDyncDataSourceContextHolder {21

22 private static final ThreadLocal contextHolder = new ThreadLocal();23

24 public static List dataSourceNames = new ArrayList();25

26 public static voidsetDataSource(String dataSourceName) {27 contextHolder.set(dataSourceName);28 }29

30 public staticString getDataSource() {31 returncontextHolder.get();32 }33

34 public static voidclearDataSource() {35 contextHolder.remove();36 }37

38 public static booleancontainsDataSource(String dataSourceName) {39 returndataSourceNames.contains(dataSourceName);40 }41 }42

43 /**

44 *45 *@authorjasoHsu46 * 动态数据源在切换,将数据源设置到ThreadLocal对象中47 */

48 @Component49 @Order(-10)50 @Aspect51 public classDyncDataSourceInterceptor {52

53 @Before("@annotation(targetDataSource)")54 public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throwsException {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 voidresetDataSource(JoinPoint point, TargetDataSource targetDataSource) {65 DyncDataSourceContextHolder.clearDataSource();66 }67 }68

69 //70 public class DyncRouteDataSource extendsAbstractRoutingDataSource {71 @Override72 protectedObject determineCurrentLookupKey() {73 returnDyncDataSourceContextHolder.getDataSource();74 }75 publicDataSource findTargetDataSource() {76 return this.determineTargetDataSource();77 }78 }

4.与mybatis集成,其实这一步与前面的基本一致。

只是将在sessionFactory以及数据管理器中,将动态数据源注册进去【dynamicRouteDataSource】,而非之前的静态数据源【getMasterDataSource()】

1 @Resource2 DyncRouteDataSource dynamicRouteDataSource;3

4 @Bean(name = "sqlSessionFactory")5 public SqlSessionFactory getinvestListingSqlSessionFactory() throwsException {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 returnsqlSessionFactory;12 }13

14

15 @Bean(name = "txManager")16 publicPlatformTransactionManager txManager() {17 return newDataSourceTransactionManager(dynamicRouteDataSource);18 }

5.核心配置参数:

1 spring:2 dataSource:3 happyboot:4 driverClassName: com.mysql.jdbc.Driver5 url: jdbc:mysql://localhost:3306/happy_springboot?characterEncoding=utf8&useSSL=true

6 username: root7 password: admin8 extdsnames: happyboot01,happyboot029 happyboot01:10 driverClassName: com.mysql.jdbc.Driver11 url: jdbc:mysql://localhost:3306/happyboot01?characterEncoding=utf8&useSSL=true

12 username: root13 password: admin14 happyboot02:15 driverClassName: com.mysql.jdbc.Driver16 url: jdbc:mysql://localhost:3306/happyboot02?characterEncoding=utf8&useSSL=true

17 username: root18 password: admin

6.应用代码

1 @Service2 public class UserExtServiceImpl implementsIUserExtService {3 @Autowired4 privateUserInfoMapper userInfoMapper;5 @TargetDataSource(value="happyboot01")6 @Override7 publicUserInfo getUserBy(Long id) {8 returnuserInfoMapper.selectByPrimaryKey(id);9 }10 }

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值