Bootstrap

深入解析多数据源管理方案及其实现过程

引言

随着信息技术的飞速发展,企业和组织的数据量呈指数级增长,传统的单数据库架构已难以满足日益复杂的业务需求。为了提高系统的性能、可靠性和扩展性,越来越多的应用程序开始采用多数据源架构。本文将详细介绍多数据源的概念、设计原则,并深入探讨几种常见的实现方案,包括使用Spring提供的 AbstractRoutingDataSource、注册多个 SqlSessionFactory 以及利用 dynamic-datasource 框架。此外,我们还会结合电商后台项目的具体实践案例来说明这些技术的实际应用。

多数据源的基本概念与设计原则

1. 什么是多数据源?

多数据源指的是在一个应用程序中同时连接并操作两个或更多个独立的数据库实例。通过这种方式,可以实现读写分离、负载均衡、异地容灾等多种高级特性,从而提升整个系统的性能和可用性。

2. 设计原则

  • 透明化访问:应用程序应该能够以统一的方式访问不同的数据源,而无需关心底层的具体实现细节。
  • 高可用性:确保即使某个数据源出现故障时,系统仍然能够正常运行。
  • 性能优化:合理分配查询请求到不同的数据源上,避免单一节点成为瓶颈。
  • 易于维护:保持配置简单明了,便于后续的管理和升级。
实现多数据源的技术方案
方案一:基于Spring的 AbstractRoutingDataSource

概述
AbstractRoutingDataSource 是Spring框架提供的一个抽象类,它允许开发者自定义逻辑来决定在每次数据库操作时应使用哪个实际的数据源。这种方式特别适用于主从分离(Master-Slave)架构,在这种架构下,写操作通常发生在主库上,而读操作则分散到多个从库中执行。

实现步骤

  1. 创建一个新的Java类继承自 AbstractRoutingDataSource,例如命名为 DynamicDataSource
  2. 在该类内部使用线程局部变量(ThreadLocal)存储当前线程所使用的数据源标识符。
  3. 覆盖 determineCurrentLookupKey() 方法,在其中返回当前线程对应的标识符。
  4. 定义多个实际的数据源Bean,并将其注入到 DynamicDataSource 中。
  5. 在需要切换数据源的地方(如Service层),设置线程局部变量为所需的标识符值。
@Component
@Primary // 将该Bean设置为主要注入Bean
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    @Override
    protected Object determineCurrentLookupKey() {
        return contextHolder.get();
    }

    public void setReadDataSource(DataSource dataSource) {
        Map<Object, Object> targetDataSources = this.getTargetDataSources();
        if (targetDataSources == null) {
            targetDataSources = new HashMap<>();
        }
        targetDataSources.put("read", dataSource);
        this.setTargetDataSources(targetDataSources);
    }

    public void setWriteDataSource(DataSource dataSource) {
        Map<Object, Object> targetDataSources = this.getTargetDataSources();
        if (targetDataSources == null) {
            targetDataSources = new HashMap<>();
        }
        targetDataSources.put("write", dataSource);
        this.setDefaultTargetDataSource(dataSource); // 默认指向写库
    }

    public static void setDbType(String dbType) {
        contextHolder.set(dbType);
    }

    public static String getDbType() {
        return contextHolder.get();
    }
}
  1. 利用AOP切面编程(Aspect Oriented Programming),可以在不修改原有业务代码的情况下,动态地根据方法上的注解来选择合适的数据源。
@Component
@Aspect
public class DataSourceAspect {

    @Around("@annotation(com.example.annotation.DataSource)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource != null) {
            DynamicDataSource.setDbType(dataSource.value());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDbType();
        }
    }
}
  1. 在具体的业务方法上添加自定义注解 @DataSource("read")@DataSource("write") 来指定要使用的数据源。
方案二:注册多个 SqlSessionFactory

概述
对于MyBatis用户来说,如果想要支持多数据源,则需要为每个数据源创建独立的 SqlSessionFactory 实例,并且可以通过配置不同的Mapper接口和XML文件路径来进行区分。这种方法适合于较为复杂的多数据源需求,尤其是当不同业务模块需要独立的数据源时。

实现步骤

  1. 在Spring Boot配置文件中声明多个数据源属性。
  2. 编写相应的配置类,分别为每个数据源创建 DataSource Bean。
  3. 为每个 DataSource 创建对应的 SqlSessionFactory Bean。
  4. 配置事务管理器(DataSourceTransactionManager),确保每个数据源都有自己的事务管理机制。
  5. 使用 @MapperScan 注解明确指定各个 SqlSessionFactory 对应的Mapper包路径。
  6. 在业务逻辑层,通过构造函数注入或setter方法注入所需的数据源对应的 SqlSessionTemplateSqlSession
@Configuration
@MapperScan(basePackages = "com.example.mapper.read", sqlSessionTemplateRef = "readSqlSessionTemplate")
public class ReadDataSourceConfig {

    @Bean(name = "readDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.read")
    public DataSource readDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "readSqlSessionFactory")
    public SqlSessionFactory readSqlSessionFactory(@Qualifier("readDataSource") DataSource readDataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(readDataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/read/*.xml"));
        return sessionFactory.getObject();
    }

    @Bean(name = "readTransactionManager")
    public DataSourceTransactionManager readTransactionManager(@Qualifier("readDataSource") DataSource readDataSource) {
        return new DataSourceTransactionManager(readDataSource);
    }

    @Bean(name = "readSqlSessionTemplate")
    public SqlSessionTemplate readSqlSessionTemplate(@Qualifier("readSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

重复上述过程为其他数据源创建类似的配置。

方案三:使用 dynamic-datasource 框架

概述
dynamic-datasource 是一个由MyBatis-Plus作者开发的开源多数据源解决方案,旨在简化多数据源配置的过程。它使得开发者可以在Spring Boot应用中轻松配置多个数据源,并且只需在方法或类上添加 @DS 注解就能实现数据源的自动切换。此框架具有配置简便、灵活的数据源切换以及良好的集成性等优点。

实现步骤

  1. 在项目的 pom.xml 文件中引入 dynamic-datasource-spring-boot-starter 依赖:
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>最新版本号</version>
</dependency>
  1. application.ymlapplication.properties 文件中配置多个数据源信息:
spring:
  datasource:
    dynamic:
      primary: master # 设置默认的数据源或者数据源组,默认值即为master
      strict: false   # 严格匹配数据源, 默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1:
          url: jdbc:mysql://localhost:3306/slave_db?useUnicode=true&characterEncoding=utf8&useSSL=false
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
  1. 在需要切换数据源的方法或类上添加 @DS 注解,指定要使用的数据源名称:
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @DS("slave_1")
    public List<User> listUsers() {
        return userMapper.selectList(null);
    }
}
  1. 如果有分库分表的需求,还可以进一步配置路由规则,以实现更复杂的数据源切换逻辑。
结论

综上所述,多数据源管理是现代软件开发中不可或缺的一部分,尤其是在处理大规模数据和高并发场景时。通过选择合适的多数据源方案——无论是基于Spring的 AbstractRoutingDataSource、注册多个 SqlSessionFactory 还是使用 dynamic-datasource 框架——都可以有效地提升系统的性能、可靠性和可扩展性。每种方案都有其独特的优势和适用场景,因此在实际项目中应根据具体的业务需求和技术栈进行权衡和选择。

;