亲手教你SpringBoot中的多数据源集成问题

 更新时间:2022年03月08日 10:59:57   作者:java不简单  
本文主要是介绍基于springboot的多数据源切换,轻量级的一种集成方案,对于小型的应用可以采用这种方案,我之前在项目中用到是因为简单,便于扩展以及优化,对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方法,进行数据源的切换。

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);
    }

@Override
public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        return dataSource;

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

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

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

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

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

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
    // 是否分库
    boolean isSharding() default false;
    // 获取分库策略
    String strategy() default "";
}
// 动态数据源上下文
public class DyncDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static List<String> dataSourceNames = new ArrayList<String>();
    public static void setDataSource(String dataSourceName) {
        contextHolder.set(dataSourceName);
    }
    public static String getDataSource() {
        return contextHolder.get();
    public static void clearDataSource() {
        contextHolder.remove();
    public static boolean containsDataSource(String dataSourceName) {
        return dataSourceNames.contains(dataSourceName);
/**
 *
 * @author jasoHsu
 * 动态数据源在切换,将数据源设置到ThreadLocal对象中
 */
@Component
@Order(-10)
@Aspect
public class DyncDataSourceInterceptor {
    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Exception {
        String dbIndx = null;
        String targetDataSourceName = targetDataSource.value() + (dbIndx == null ? "" : dbIndx);
        if (DyncDataSourceContextHolder.containsDataSource(targetDataSourceName)) {
            DyncDataSourceContextHolder.setDataSource(targetDataSourceName);
        }
    @After("@annotation(targetDataSource)")
    public void resetDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        DyncDataSourceContextHolder.clearDataSource();
//
public class DyncRouteDataSource extends AbstractRoutingDataSource {
@Override
    protected Object determineCurrentLookupKey() {
        return DyncDataSourceContextHolder.getDataSource();
    public DataSource findTargetDataSource() {
        return this.determineTargetDataSource();

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

@Resource
    DyncRouteDataSource dynamicRouteDataSource;

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory getinvestListingSqlSessionFactory() throws Exception {
        String configLocation = "classpath:/conf/mybatis/configuration.xml";
        String mapperLocation = "classpath*:/com/happy/springboot/service/dao/*/*Mapper.xml";
        SqlSessionFactory sqlSessionFactory = createDefaultSqlSessionFactory(dynamicRouteDataSource, configLocation,
                mapperLocation);
        return sqlSessionFactory;
    }
    @Bean(name = "txManager")
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dynamicRouteDataSource);

5.核心配置参数:

spring:
  dataSource:
    happyboot:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/happy_springboot?characterEncoding=utf8&useSSL=true
      username: root
      password: admin
    extdsnames: happyboot01,happyboot02
    happyboot01:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/happyboot01?characterEncoding=utf8&useSSL=true
      username: root
      password: admin
    happyboot02:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/happyboot02?characterEncoding=utf8&useSSL=true
      username: root
      password: admin

6.应用代码

@Service
public class UserExtServiceImpl implements IUserExtService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @TargetDataSource(value="happyboot01")
    @Override
    public UserInfo getUserBy(Long id) {
        return userInfoMapper.selectByPrimaryKey(id);
    }
}

到此这篇关于关于SpringBoot中的多数据源集成的文章就介绍到这了,更多相关SpringBoot多数据源集成内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java回调机制解读

    Java回调机制解读

    本文主要介绍了Java回调机制的相关知识,具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • Java 冒泡排序、快速排序实例代码

    Java 冒泡排序、快速排序实例代码

    这篇文章主要介绍了Java 冒泡排序、快速排序实例代码,需要的朋友可以参考下
    2017-05-05
  • Java中controller层如何接收带参数的查询

    Java中controller层如何接收带参数的查询

    本文主要介绍了Java中controller层如何接收带参数的查询,在控制器层接收带参数的查询可以通过多种方式实现,下面就详细的介绍一下,感兴趣的可以了解一下
    2023-08-08
  • Java继承的问题引导和测试代码

    Java继承的问题引导和测试代码

    这篇文章主要介绍了Java继承的问题引导和测试代码,涉及继承的概念,合成的语法等相关内容,具有一定借鉴价值,需要的朋友可以参考下。
    2021-08-08
  • SpringBoot整合RabbitMQ的5种模式实战

    SpringBoot整合RabbitMQ的5种模式实战

    本文主要介绍了SpringBoot整合RabbitMQ的5种模式实战,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • Mybatis-plus插入后返回元素id的问题

    Mybatis-plus插入后返回元素id的问题

    这篇文章主要介绍了Mybatis-plus插入后返回元素id的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • 使用SpringBoot中整合Redis

    使用SpringBoot中整合Redis

    这篇文章主要介绍了使用SpringBoot中整合Redis,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Java SpringBoot整合shiro-spring-boot-starterqi项目报错解决

    Java SpringBoot整合shiro-spring-boot-starterqi项目报错解决

    这篇文章主要介绍了Java SpringBoot整合shiro-spring-boot-starterqi项目报错解决,文章围绕主题展开详细的内容介绍,具有一定的参考一下
    2022-08-08
  • Java自定义映射resultMap定义及用法

    Java自定义映射resultMap定义及用法

    MyBatis的每一个查询映射的返回类型都是ResultMap,当我们提供返回类型属性是resultType时,MyBatis会自动给我们把对应值赋给resultType所指定对象的属性,当我们提供返回类型是resultMap时,将数据库中列数据复制到对象的相应属性上,可以用于复制查询,两者不能同时用
    2022-11-11
  • MyBatis一二级缓存

    MyBatis一二级缓存

    这篇文章主要介绍了MyBatis一二级缓存的相关知识,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-06-06

最新评论