SpringBoot多数据源配置详解:从基础到高级实践

1. 多数据源概述与核心概念

1.1 什么是多数据源?

多数据源是指在一个应用程序中同时连接和使用多个数据库的能力。在实际开发中,我们经常会遇到以下场景需要多数据源:

  • 同时连接生产数据库和报表数据库
  • 读写分离场景(主库写,从库读)
  • 微服务架构中需要访问其他服务的数据库
  • 多租户系统中每个租户有独立数据库

1.2 核心组件解析

组件作用类比解释
DataSource数据库连接池,管理数据库连接像是一个自来水厂,管理着到各个小区(数据库)的水管(连接)
EntityManagerJPA实体管理器,负责持久化操作像是小区的物业管理员,负责管理小区内(数据库)的各种事务
TransactionManager事务管理器,协调跨数据源的事务像是跨小区的协调员,确保多个小区的事务能同步进行

1.3 多数据源实现方式对比

实现方式优点缺点适用场景
抽象路由(AbstractRoutingDataSource)灵活,动态切换配置复杂需要运行时动态切换数据源
独立配置多数据源清晰明确代码冗余固定的多个数据源
JPA多持久化单元符合JPA规范配置复杂使用JPA且需要严格规范的项目

2. 基础配置:两种数据源实现

2.1 基于配置文件的简单多数据源

  • application.yml配置示例:
spring:
  datasource:
    primary:  # 主数据源
      jdbc-url: jdbc:mysql://localhost:3306/primary_db?useSSL=false
      username: root
      password: root123
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:  # 从数据源
      jdbc-url: jdbc:mysql://localhost:3306/secondary_db?useSSL=false
      username: root
      password: root123
      driver-class-name: com.mysql.cj.jdbc.Driver
  • 数据源配置类:
@Configuration
public class DataSourceConfig {
    
    // 主数据源
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    // 从数据源
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}
  • 配置事务管理器:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;

@Configuration
public class TransactionManagerConfig {

    @Bean(name = "primaryTransactionManager")
    public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "secondaryTransactionManager")
    public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

使用示例:

@Service
public class UserService {
    
    // 使用JdbcTemplate操作主数据源
    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;
    
    // 使用JdbcTemplate操作从数据源
    @Autowired
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;
    
    public void addUserToBothDBs(User user) {
        JdbcTemplate primaryJdbcTemplate = new JdbcTemplate(primaryDataSource);
        JdbcTemplate secondaryJdbcTemplate = new JdbcTemplate(secondaryDataSource);
        
        String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
        
        primaryJdbcTemplate.update(sql, user.getUsername(), user.getEmail());
        secondaryJdbcTemplate.update(sql, user.getUsername(), user.getEmail());
    }
}

2.2 基于AbstractRoutingDataSource的动态数据源

动态数据源上下文:

public class DynamicDataSourceContextHolder {
    // 使用ThreadLocal保证线程安全
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
    // 数据源列表
    public static final String PRIMARY_DS = "primary";
    public static final String SECONDARY_DS = "secondary";
    
    public static void setDataSourceType(String dsType) {
        CONTEXT_HOLDER.set(dsType);
    }
    
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }
    
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

动态数据源配置:

// 表明该类是一个配置类,Spring 会对其进行扫描并应用其中的配置信息
@Configuration
public class DynamicDataSourceConfig {

    /**
     * 创建主数据源 Bean
     * @return 主数据源实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 从配置文件中读取以 "spring.datasource.primary" 为前缀的配置信息来初始化数据源
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        // 使用 DataSourceBuilder 创建数据源实例
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建从数据源 Bean
     * @return 从数据源实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 从配置文件中读取以 "spring.datasource.secondary" 为前缀的配置信息来初始化数据源
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        // 使用 DataSourceBuilder 创建数据源实例
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建动态数据源 Bean,并将其设置为主要的数据源 Bean
     * @return 动态数据源实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 标记该 Bean 为主要的 Bean,当有多个同类型的 Bean 时,优先使用该 Bean
    @Primary
    public DataSource dynamicDataSource() {
        // 用于存储目标数据源的映射,键为数据源标识,值为数据源实例
        Map<Object, Object> targetDataSources = new HashMap<>();
        // 将主数据源添加到目标数据源映射中,使用自定义的主数据源标识
        targetDataSources.put(DynamicDataSourceContextHolder.PRIMARY_DS, primaryDataSource());
        // 将从数据源添加到目标数据源映射中,使用自定义的从数据源标识
        targetDataSources.put(DynamicDataSourceContextHolder.SECONDARY_DS, secondaryDataSource());

        // 创建自定义的动态数据源实例
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 设置动态数据源的目标数据源映射
        dynamicDataSource.setTargetDataSources(targetDataSources);
        // 设置动态数据源的默认目标数据源为主数据源
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());

        return dynamicDataSource;
    }

    // 自定义动态数据源类,继承自 AbstractRoutingDataSource
    private static class DynamicDataSource extends AbstractRoutingDataSource {
        /**
         * 确定当前要使用的数据源的标识
         * @return 当前数据源的标识
         */
        @Override
        protected Object determineCurrentLookupKey() {
            // 从上下文持有者中获取当前要使用的数据源类型
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    }
}

使用示例:

@Service
public class OrderService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void processOrder(Long orderId) {
        // 默认使用主数据源
        Order order = getOrderFromPrimary(orderId);
        
        try {
            // 切换到从数据源
            DynamicDataSourceContextHolder.setDataSourceType(DynamicDataSourceContextHolder.SECONDARY_DS);
            
            // 在从数据源中记录订单日志
            logOrderToSecondary(order);
        } finally {
            // 恢复数据源
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
    
    private Order getOrderFromPrimary(Long orderId) {
        String sql = "SELECT * FROM orders WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{orderId}, 
            (rs, rowNum) -> new Order(
                rs.getLong("id"),
                rs.getString("order_no"),
                rs.getBigDecimal("amount")
            ));
    }
    
    private void logOrderToSecondary(Order order) {
        String sql = "INSERT INTO order_logs (order_id, order_no, amount) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, order.getId(), order.getOrderNo(), order.getAmount());
    }
}

3. 高级配置:集成MyBatis与JPA

3.1 MyBatis多数据源配置

主数据源MyBatis配置:

// 表明这是一个配置类,Spring 会对其进行扫描并应用其中的配置信息
@Configuration
// 配置 MyBatis Mapper 接口的扫描路径和对应的 SqlSessionFactory 引用
// basePackages 指定要扫描的 Mapper 接口所在的包路径
// sqlSessionFactoryRef 指定使用的 SqlSessionFactory 的 Bean 名称
@MapperScan(
    basePackages = "com.example.mapper.primary",
    sqlSessionFactoryRef = "primarySqlSessionFactory"
)
public class PrimaryMyBatisConfig {

    /**
     * 创建主数据源的 Bean
     * @return 主数据源实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 从配置文件中读取以 "spring.datasource.primary" 为前缀的配置信息来初始化数据源
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        // 使用 DataSourceBuilder 创建数据源实例
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建主数据源对应的 SqlSessionFactory Bean
     * @param dataSource 主数据源实例
     * @return 主数据源对应的 SqlSessionFactory 实例
     * @throws Exception 创建过程中可能抛出的异常
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) 
        throws Exception {
        // 创建 SqlSessionFactoryBean 实例,用于创建 SqlSessionFactory
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 设置 SqlSessionFactory 使用的数据源
        sessionFactory.setDataSource(dataSource);
        // 设置 Mapper XML 文件的位置,使用 PathMatchingResourcePatternResolver 来查找匹配的资源
        sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/primary/*.xml"));
        // 获取并返回 SqlSessionFactory 实例
        return sessionFactory.getObject();
    }

    /**
     * 创建主数据源对应的 SqlSessionTemplate Bean
     * @param sqlSessionFactory 主数据源对应的 SqlSessionFactory 实例
     * @return 主数据源对应的 SqlSessionTemplate 实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    public SqlSessionTemplate primarySqlSessionTemplate(
        @Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        // 创建并返回 SqlSessionTemplate 实例,用于简化 MyBatis 的操作
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

从数据源MyBatis配置:

// 该注解表明这是一个 Spring 配置类,Spring 框架会自动扫描并处理这个类中的配置信息
@Configuration
// 此注解用于指定 MyBatis Mapper 接口的扫描范围和对应的 SqlSessionFactory 引用
// basePackages 表示要扫描的 Mapper 接口所在的基础包路径
// sqlSessionFactoryRef 指明使用的 SqlSessionFactory Bean 的名称
@MapperScan(
    basePackages = "com.example.mapper.secondary",
    sqlSessionFactoryRef = "secondarySqlSessionFactory"
)
public class SecondaryMyBatisConfig {

    /**
     * 创建并配置从数据源的 Bean
     * @return 配置好的从数据源实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 从配置文件里读取以 "spring.datasource.secondary" 为前缀的配置项,用于初始化数据源
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        // 利用 DataSourceBuilder 创建数据源实例
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建并配置从数据源对应的 SqlSessionFactory Bean
     * @param dataSource 从数据源实例
     * @return 配置好的从数据源对应的 SqlSessionFactory 实例
     * @throws Exception 创建过程中可能出现的异常,如配置文件读取错误等
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) 
        throws Exception {
        // 创建一个 SqlSessionFactoryBean 实例,用于构建 SqlSessionFactory
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 为 SqlSessionFactory 设置使用的数据源
        sessionFactory.setDataSource(dataSource);
        // 配置 Mapper XML 文件的位置,使用 PathMatchingResourcePatternResolver 来查找符合条件的资源
        sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/secondary/*.xml"));
        // 通过 SqlSessionFactoryBean 获取最终的 SqlSessionFactory 实例
        return sessionFactory.getObject();
    }

    /**
     * 创建并配置从数据源对应的 SqlSessionTemplate Bean
     * @param sqlSessionFactory 从数据源对应的 SqlSessionFactory 实例
     * @return 配置好的从数据源对应的 SqlSessionTemplate 实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    public SqlSessionTemplate secondarySqlSessionTemplate(
        @Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        // 基于给定的 SqlSessionFactory 创建一个 SqlSessionTemplate 实例,简化 MyBatis 的操作
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

使用示例:

@Service
public class ProductService {
    
    @Autowired
    @Qualifier("primarySqlSessionTemplate")
    private SqlSessionTemplate primarySqlSessionTemplate;
    
    @Autowired
    @Qualifier("secondarySqlSessionTemplate")
    private SqlSessionTemplate secondarySqlSessionTemplate;
    
    public void syncProduct(Long productId) {
        // 从主数据源获取产品
        ProductMapper primaryMapper = primarySqlSessionTemplate.getMapper(ProductMapper.class);
        Product product = primaryMapper.selectById(productId);
        
        // 同步到从数据源
        ProductMapper secondaryMapper = secondarySqlSessionTemplate.getMapper(ProductMapper.class);
        if (secondaryMapper.selectById(productId) == null) {
            secondaryMapper.insert(product);
        } else {
            secondaryMapper.update(product);
        }
    }
}

3.2 JPA多数据源配置

主数据源JPA配置:

// 表明这是一个 Spring 配置类,Spring 会自动扫描并处理该类中的配置信息
@Configuration
// 启用 Spring 的事务管理功能,允许使用 @Transactional 注解来管理事务
@EnableTransactionManagement
// 启用 JPA 仓库的自动扫描和注册功能
// basePackages 指定要扫描的 JPA 仓库接口所在的包路径
// entityManagerFactoryRef 指定使用的实体管理器工厂的 Bean 名称
// transactionManagerRef 指定使用的事务管理器的 Bean 名称
@EnableJpaRepositories(
    basePackages = "com.example.repository.primary",
    entityManagerFactoryRef = "primaryEntityManagerFactory",
    transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryJpaConfig {

    /**
     * 创建主数据源的 Bean,并将其标记为主要的数据源 Bean
     * @return 主数据源实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 标记该 Bean 为主要的 Bean,当有多个同类型的 Bean 时,优先使用该 Bean
    @Primary
    // 从配置文件中读取以 "spring.datasource.primary" 为前缀的配置信息来初始化数据源
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        // 使用 DataSourceBuilder 创建数据源实例
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建主数据源对应的实体管理器工厂的 Bean,并将其标记为主要的实体管理器工厂 Bean
     * @param builder 实体管理器工厂构建器
     * @param dataSource 主数据源实例
     * @return 主数据源对应的实体管理器工厂 Bean
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 标记该 Bean 为主要的 Bean,当有多个同类型的 Bean 时,优先使用该 Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
        EntityManagerFactoryBuilder builder, 
        @Qualifier("primaryDataSource") DataSource dataSource) {
        // 使用实体管理器工厂构建器构建实体管理器工厂
        return builder
            // 设置数据源
            .dataSource(dataSource)
            // 指定要扫描的实体类所在的包路径
            .packages("com.example.entity.primary")
            // 设置持久化单元的名称
            .persistenceUnit("primaryPersistenceUnit")
            // 设置 JPA 的属性
            .properties(jpaProperties())
            .build();
    }

    /**
     * 创建主数据源对应的事务管理器的 Bean,并将其标记为主要的事务管理器 Bean
     * @param entityManagerFactory 主数据源对应的实体管理器工厂实例
     * @return 主数据源对应的事务管理器实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 标记该 Bean 为主要的 Bean,当有多个同类型的 Bean 时,优先使用该 Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager(
        @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        // 创建 JPA 事务管理器实例
        return new JpaTransactionManager(entityManagerFactory);
    }

    /**
     * 配置 JPA 的属性
     * @return 包含 JPA 属性的 Map
     */
    private Map<String, Object> jpaProperties() {
        // 创建一个用于存储 JPA 属性的 Map
        Map<String, Object> props = new HashMap<>();
        // 设置 Hibernate 使用的数据库方言为 MySQL 8
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
        // 设置 Hibernate 在启动时自动更新数据库表结构
        props.put("hibernate.hbm2ddl.auto", "update");
        // 开启显示 SQL 语句的功能
        props.put("hibernate.show_sql", true);
        // 开启格式化 SQL 语句的功能,使 SQL 语句更易读
        props.put("hibernate.format_sql", true);
        return props;
    }
}

从数据源JPA配置:

// 表明该类是一个 Spring 配置类,Spring 容器会自动扫描并加载其中的配置信息
@Configuration
// 启用 Spring 的事务管理功能,这样就可以在服务层等地方使用 @Transactional 注解来管理事务
@EnableTransactionManagement
// 开启 JPA 仓库的自动扫描和注册功能
// basePackages 指定要扫描的 JPA 仓库接口所在的包路径
// entityManagerFactoryRef 指定使用的实体管理器工厂的 Bean 名称
// transactionManagerRef 指定使用的事务管理器的 Bean 名称
@EnableJpaRepositories(
    basePackages = "com.example.repository.secondary",
    entityManagerFactoryRef = "secondaryEntityManagerFactory",
    transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryJpaConfig {

    /**
     * 创建并配置从数据源的 Bean
     * @return 配置好的从数据源实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    // 从配置文件里读取以 "spring.datasource.secondary" 为前缀的配置项,用于初始化数据源
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        // 利用 DataSourceBuilder 创建数据源实例
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建并配置从数据源对应的实体管理器工厂的 Bean
     * @param builder 用于构建实体管理器工厂的构建器
     * @param dataSource 从数据源实例
     * @return 配置好的从数据源对应的实体管理器工厂 Bean
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
        EntityManagerFactoryBuilder builder, 
        @Qualifier("secondaryDataSource") DataSource dataSource) {
        return builder
            // 设置使用的数据源为从数据源
            .dataSource(dataSource)
            // 指定要扫描的实体类所在的包路径
            .packages("com.example.entity.secondary")
            // 设置持久化单元的名称
            .persistenceUnit("secondaryPersistenceUnit")
            // 设置 JPA 的相关属性
            .properties(jpaProperties())
            // 构建并返回实体管理器工厂 Bean
            .build();
    }

    /**
     * 创建并配置从数据源对应的事务管理器的 Bean
     * @param entityManagerFactory 从数据源对应的实体管理器工厂实例
     * @return 配置好的从数据源对应的事务管理器实例
     */
    // 将该方法返回的对象注册为一个 Spring Bean
    @Bean
    public PlatformTransactionManager secondaryTransactionManager(
        @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        // 创建一个基于 JPA 的事务管理器,并将从数据源对应的实体管理器工厂传入
        return new JpaTransactionManager(entityManagerFactory);
    }

    /**
     * 配置 JPA 的属性
     * @return 包含 JPA 属性的 Map
     */
    private Map<String, Object> jpaProperties() {
        // 创建一个用于存储 JPA 属性的 Map
        Map<String, Object> props = new HashMap<>();
        // 设置 Hibernate 使用的数据库方言为 MySQL 8
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
        // 设置 Hibernate 在启动时自动更新数据库表结构
        props.put("hibernate.hbm2ddl.auto", "update");
        // 开启显示 SQL 语句的功能
        props.put("hibernate.show_sql", true);
        // 开启格式化 SQL 语句的功能,使 SQL 语句更易读
        props.put("hibernate.format_sql", true);
        return props;
    }
}

使用示例:

@Service
public class CustomerService {
    
    @Autowired
    @Qualifier("primaryTransactionManager")
    private PlatformTransactionManager primaryTransactionManager;
    
    @Autowired
    @Qualifier("secondaryTransactionManager")
    private PlatformTransactionManager secondaryTransactionManager;
    
    @Autowired
    private PrimaryCustomerRepository primaryCustomerRepository;
    
    @Autowired
    private SecondaryCustomerRepository secondaryCustomerRepository;
    
    public void migrateCustomer(Long customerId) {
        // 从主数据源获取客户
        Customer customer = primaryCustomerRepository.findById(customerId)
            .orElseThrow(() -> new RuntimeException("Customer not found"));
        
        // 定义事务属性
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        
        // 在从数据源事务中保存客户
        TransactionStatus status = secondaryTransactionManager.getTransaction(definition);
        try {
            secondaryCustomerRepository.save(customer);
            secondaryTransactionManager.commit(status);
        } catch (Exception e) {
            secondaryTransactionManager.rollback(status);
            throw e;
        }
    }
}

4. 事务管理:跨数据源事务处理

4.1 单数据源事务

在单数据源场景下,Spring的事务管理非常简单:

@Service
public class AccountService {
    
    @Autowired
    private AccountRepository accountRepository;
    
    @Transactional  // 使用默认事务管理器
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        Account fromAccount = accountRepository.findById(fromId)
            .orElseThrow(() -> new RuntimeException("Account not found: " + fromId));
        Account toAccount = accountRepository.findById(toId)
            .orElseThrow(() -> new RuntimeException("Account not found: " + toId));
        
        fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
        toAccount.setBalance(toAccount.getBalance().add(amount));
        
        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
    }
}

4.2 多数据源事务挑战

多数据源事务面临的主要问题是分布式事务的挑战。Spring的@Transactional注解默认只能管理单个事务管理器,无法直接协调多个数据源的事务。

解决方案对比:

方案原理优点缺点适用场景
JTA (Java Transaction API)使用全局事务协调器强一致性性能开销大,配置复杂需要强一致性的金融系统
最终一致性 (Saga模式)通过补偿操作实现高性能,松耦合实现复杂,需要补偿逻辑高并发,可接受短暂不一致
本地消息表通过消息队列保证可靠性高需要额外表存储消息需要可靠异步处理的场景

4.3 JTA实现多数据源事务

Atomikos配置示例:

// 表明这是一个 Spring 配置类,Spring 容器会自动扫描并加载该类中的配置信息
@Configuration
// 启用 Spring 的声明式事务管理功能,允许使用 @Transactional 注解来管理事务
@EnableTransactionManagement
public class JtaDataSourceConfig {

    /**
     * 创建并配置 UserTransactionManager Bean
     * UserTransactionManager 用于管理用户事务,支持分布式事务处理
     * @return 配置好的 UserTransactionManager 实例
     * @throws SystemException 当系统出现异常时抛出
     */
    // 定义一个 Bean,指定初始化方法为 init,销毁方法为 close
    @Bean(initMethod = "init", destroyMethod = "close")
    public UserTransactionManager userTransactionManager() throws SystemException {
        // 创建 UserTransactionManager 实例
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        // 设置是否强制关闭事务管理器,false 表示不强制关闭
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

    /**
     * 创建并配置 UserTransaction Bean
     * UserTransaction 用于表示用户发起的事务操作
     * @return 配置好的 UserTransaction 实例
     * @throws SystemException 当系统出现异常时抛出
     */
    @Bean
    public UserTransaction userTransaction() throws SystemException {
        // 创建 UserTransactionImp 实例,它是 UserTransaction 的一个实现类
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        // 设置事务的超时时间为 300 秒
        userTransactionImp.setTransactionTimeout(300);
        return userTransactionImp;
    }

    /**
     * 创建并配置 JtaTransactionManager Bean
     * JtaTransactionManager 是 Spring 提供的用于管理 JTA(Java Transaction API)事务的管理器
     * @return 配置好的 JtaTransactionManager 实例
     * @throws SystemException 当系统出现异常时抛出
     */
    @Bean
    public JtaTransactionManager transactionManager() throws SystemException {
        // 创建 JtaTransactionManager 实例
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        // 设置 UserTransaction,用于管理用户发起的事务
        jtaTransactionManager.setUserTransaction(userTransaction());
        // 设置 TransactionManager,用于管理事务的生命周期
        jtaTransactionManager.setTransactionManager(userTransactionManager());
        return jtaTransactionManager;
    }

    /**
     * 创建并配置主数据源 Bean
     * 主数据源使用 XA 数据源,支持分布式事务
     * @return 配置好的主数据源实例
     */
    // 将该 Bean 标记为主要的 Bean,当有多个数据源 Bean 时,默认使用该数据源
    @Bean
    @Primary
    // 从配置文件中读取以 "spring.datasource.primary" 为前缀的配置信息
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        // 创建 MySQL 的 XA 数据源实例
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        // 从环境变量中获取主数据源的 JDBC URL 并设置到 XA 数据源中
        mysqlXaDataSource.setUrl(env.getProperty("spring.datasource.primary.jdbc-url"));
        // 从环境变量中获取主数据源的用户名并设置到 XA 数据源中
        mysqlXaDataSource.setUser(env.getProperty("spring.datasource.primary.username"));
        // 从环境变量中获取主数据源的密码并设置到 XA 数据源中
        mysqlXaDataSource.setPassword(env.getProperty("spring.datasource.primary.password"));

        // 创建 Atomikos 的 XA 数据源包装器实例
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        // 将 MySQL 的 XA 数据源设置到 Atomikos 的数据源包装器中
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        // 为该数据源设置唯一的资源名称
        xaDataSource.setUniqueResourceName("primaryDB");
        // 设置数据源的连接池大小为 5
        xaDataSource.setPoolSize(5);
        return xaDataSource;
    }

    /**
     * 创建并配置从数据源 Bean
     * 从数据源使用 XA 数据源,支持分布式事务
     * @return 配置好的从数据源实例
     */
    @Bean
    // 从配置文件中读取以 "spring.datasource.secondary" 为前缀的配置信息
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        // 创建 MySQL 的 XA 数据源实例
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        // 从环境变量中获取从数据源的 JDBC URL 并设置到 XA 数据源中
        mysqlXaDataSource.setUrl(env.getProperty("spring.datasource.secondary.jdbc-url"));
        // 从环境变量中获取从数据源的用户名并设置到 XA 数据源中
        mysqlXaDataSource.setUser(env.getProperty("spring.datasource.secondary.username"));
        // 从环境变量中获取从数据源的密码并设置到 XA 数据源中
        mysqlXaDataSource.setPassword(env.getProperty("spring.datasource.secondary.password"));

        // 创建 Atomikos 的 XA 数据源包装器实例
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        // 将 MySQL 的 XA 数据源设置到 Atomikos 的数据源包装器中
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        // 为该数据源设置唯一的资源名称
        xaDataSource.setUniqueResourceName("secondaryDB");
        // 设置数据源的连接池大小为 5
        xaDataSource.setPoolSize(5);
        return xaDataSource;
    }
}

使用JTA事务:

@Service
public class OrderInventoryService {
    
    @Autowired
    private OrderRepository orderRepository;  // 使用主数据源
    
    @Autowired
    private InventoryRepository inventoryRepository;  // 使用从数据源
    
    @Transactional  // 现在会使用JTA事务管理器
    public void placeOrder(Order order) {
        // 在主数据源中保存订单
        orderRepository.save(order);
        
        // 在从数据源中扣减库存
        Inventory inventory = inventoryRepository.findByProductId(order.getProductId())
            .orElseThrow(() -> new RuntimeException("Inventory not found"));
        
        if (inventory.getStock() < order.getQuantity()) {
            throw new RuntimeException("Insufficient inventory");
        }
        
        inventory.setStock(inventory.getStock() - order.getQuantity());
        inventoryRepository.save(inventory);
    }
}

4.4 Saga模式实现最终一致性

Saga实现示例:

@Service
public class OrderSagaService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private SagaLogRepository sagaLogRepository;
    
    public void createOrder(Order order) {
        // 开启Saga事务
        String sagaId = UUID.randomUUID().toString();
        sagaLogRepository.save(new SagaLog(sagaId, "ORDER_CREATION", "STARTED"));
        
        try {
            // Step 1: 创建订单
            order.setStatus(OrderStatus.PENDING);
            orderRepository.save(order);
            sagaLogRepository.save(new SagaLog(sagaId, "ORDER_CREATED", "SUCCESS"));
            
            // Step 2: 扣减库存
            inventoryService.decreaseStock(sagaId, order.getProductId(), order.getQuantity());
            
            // Step 3: 完成订单
            order.setStatus(OrderStatus.COMPLETED);
            orderRepository.save(order);
            sagaLogRepository.save(new SagaLog(sagaId, "ORDER_COMPLETED", "SUCCESS"));
            
        } catch (Exception e) {
            // 失败补偿
            compensate(sagaId);
            throw e;
        }
    }
    
    private void compensate(String sagaId) {
        List<SagaLog> logs = sagaLogRepository.findBySagaId(sagaId);
        
        // 按相反顺序执行补偿操作
        if (logs.stream().anyMatch(log -> "ORDER_COMPLETED".equals(log.getStep()))) {
            // 如果订单已完成,需要取消订单
            // 实现取消逻辑...
        }
        
        if (logs.stream().anyMatch(log -> "INVENTORY_DECREASED".equals(log.getStep()))) {
            // 如果库存已扣减,需要恢复库存
            inventoryService.compensateDecrease(sagaId);
        }
        
        if (logs.stream().anyMatch(log -> "ORDER_CREATED".equals(log.getStep()))) {
            // 如果订单已创建,需要取消订单
            orderRepository.findBySagaId(sagaId).ifPresent(order -> {
                order.setStatus(OrderStatus.CANCELLED);
                orderRepository.save(order);
            });
        }
        
        sagaLogRepository.save(new SagaLog(sagaId, "SAGA_COMPENSATED", "COMPLETED"));
    }
}

5. 性能优化与最佳实践

5.1 连接池配置优化

多数据源场景下,连接池配置尤为重要。以下是HikariCP的优化配置示例:

spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/primary_db
      username: root
      password: root123
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        pool-name: PrimaryHikariPool
        maximum-pool-size: 20
        minimum-idle: 5
        idle-timeout: 30000
        max-lifetime: 1800000
        connection-timeout: 30000
        connection-test-query: SELECT 1
    secondary:
      jdbc-url: jdbc:mysql://localhost:3306/secondary_db
      username: root
      password: root123
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        pool-name: SecondaryHikariPool
        maximum-pool-size: 15
        minimum-idle: 3
        idle-timeout: 30000
        max-lifetime: 1800000
        connection-timeout: 30000
        connection-test-query: SELECT 1

关键参数解释:

参数说明推荐值设置依据
maximum-pool-size最大连接数10-50根据数据库性能和并发请求量
minimum-idle最小空闲连接3-10保证快速响应,但不过多占用资源
idle-timeout空闲连接超时时间(ms)30000避免长时间空闲连接
max-lifetime连接最大生命周期(ms)1800000定期刷新连接,防止连接老化
connection-timeout获取连接超时时间(ms)30000避免长时间等待

5.2 读写分离实现

基于AOP的读写分离实现:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ReadOnly {
    // 标记为读操作
}

@Aspect
@Component
public class ReadWriteDataSourceAspect {
    
    @Before("@annotation(readOnly)")
    public void beforeSwitchDataSource(JoinPoint point, ReadOnly readOnly) {
        DynamicDataSourceContextHolder.setDataSourceType(DynamicDataSourceContextHolder.SECONDARY_DS);
    }
    
    @After("@annotation(readOnly)")
    public void afterSwitchDataSource(JoinPoint point, ReadOnly readOnly) {
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}

使用示例:

@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Transactional
    public void createProduct(Product product) {
        // 默认使用主数据源(写)
        productRepository.save(product);
    }
    
    @ReadOnly
    @Transactional
    public Product getProduct(Long id) {
        // 使用从数据源(读)
        return productRepository.findById(id).orElse(null);
    }
    
    @ReadOnly
    @Transactional
    public List<Product> listProducts() {
        // 使用从数据源(读)
        return productRepository.findAll();
    }
}

5.3 多数据源监控

集成Druid监控:

// 表明这是一个 Spring 配置类,Spring 框架会自动扫描并处理该类中的配置信息
@Configuration
public class DruidConfig {

    /**
     * 配置 Druid 监控的 Servlet 注册 Bean
     * 该 Servlet 用于提供 Druid 监控界面,方便对数据源进行监控和管理
     * @return 配置好的 Servlet 注册 Bean
     */
    @Bean
    public ServletRegistrationBean<StatViewServlet> druidServlet() {
        // 创建一个 Servlet 注册 Bean,将 StatViewServlet 注册到 "/druid/*" 路径下
        ServletRegistrationBean<StatViewServlet> servletRegistrationBean = 
            new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

        // 设置允许访问 Druid 监控界面的 IP 白名单,这里只允许本地 127.0.0.1 访问
        servletRegistrationBean.addInitParameter("allow", "127.0.0.1");

        // 设置登录 Druid 监控界面的用户名
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        // 设置登录 Druid 监控界面的密码
        servletRegistrationBean.addInitParameter("loginPassword", "admin");

        // 设置是否允许重置监控数据,这里设置为不允许
        servletRegistrationBean.addInitParameter("resetEnable", "false");

        return servletRegistrationBean;
    }

    /**
     * 配置 Druid 监控的过滤器注册 Bean
     * 该过滤器用于收集和监控 Web 请求的相关信息
     * @return 配置好的过滤器注册 Bean
     */
    @Bean
    public FilterRegistrationBean<WebStatFilter> filterRegistrationBean() {
        // 创建一个过滤器注册 Bean,将 WebStatFilter 注册到所有请求路径上
        FilterRegistrationBean<WebStatFilter> filterRegistrationBean = 
            new FilterRegistrationBean<>(new WebStatFilter());

        // 设置过滤器的拦截路径,这里拦截所有请求
        filterRegistrationBean.addUrlPatterns("/*");

        // 设置不需要进行监控的资源路径,这些资源不会被 WebStatFilter 拦截
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

        return filterRegistrationBean;
    }

    /**
     * 创建主数据源的 Bean
     * 从配置文件中读取以 "spring.datasource.primary" 为前缀的配置信息来初始化 Druid 数据源
     * @return 主数据源实例
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        // 使用 DruidDataSourceBuilder 创建 Druid 数据源实例
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 创建从数据源的 Bean
     * 从配置文件中读取以 "spring.datasource.secondary" 为前缀的配置信息来初始化 Druid 数据源
     * @return 从数据源实例
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        // 使用 DruidDataSourceBuilder 创建 Druid 数据源实例
        return DruidDataSourceBuilder.create().build();
    }
}

监控指标说明:

指标说明健康值范围
ActiveCount活跃连接数< maximum-pool-size
WaitingThreadCount等待连接的线程数越少越好,最好为0
ConnectionHoldTime连接持有时间根据业务,通常<1s
ExecuteCount执行次数-
ErrorCount错误次数错误率<1%
FetchRowCount获取行数-

6. 常见问题与解决方案

6.1 典型问题排查表

问题现象可能原因解决方案
无法启动,报错"more than one primary"多个@Primary注解冲突确保每个数据源相关配置中只有一个@Primary
动态切换数据源无效线程污染或未正确清理确保在finally块中清理数据源上下文
事务不生效使用了错误的事务管理器确保每个Repository使用对应的事务管理器
性能低下连接池配置不合理优化连接池参数,监控连接使用情况
跨数据源事务不一致未使用分布式事务解决方案引入JTA或Saga模式

6.2 数据源切换失败案例分析

问题描述
在动态数据源切换场景下,有时切换不生效,仍然使用默认数据源。

原因分析

  1. 数据源切换代码被异常绕过,未执行
  2. 线程池场景下线程复用导致上下文污染
  3. AOP顺序问题导致切换时机不对

解决方案

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)  // 确保最先执行
public class DataSourceAspect {
    
    @Around("@annotation(targetDataSource)")
    public Object around(ProceedingJoinPoint joinPoint, TargetDataSource targetDataSource) throws Throwable {
        String oldKey = DynamicDataSourceContextHolder.getDataSourceType();
        try {
            DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
            return joinPoint.proceed();
        } finally {
            // 恢复为原来的数据源
            if (oldKey != null) {
                DynamicDataSourceContextHolder.setDataSourceType(oldKey);
            } else {
                DynamicDataSourceContextHolder.clearDataSourceType();
            }
        }
    }
}

// 线程池配置确保清理上下文
@Configuration
public class ThreadPoolConfig {
    
    @Bean
    public ExecutorService asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.setTaskDecorator(runnable -> {
            String dsKey = DynamicDataSourceContextHolder.getDataSourceType();
            return () -> {
                try {
                    if (dsKey != null) {
                        DynamicDataSourceContextHolder.setDataSourceType(dsKey);
                    }
                    runnable.run();
                } finally {
                    DynamicDataSourceContextHolder.clearDataSourceType();
                }
            };
        });
        executor.initialize();
        return executor.getThreadPoolExecutor();
    }
}

6.3 多数据源与缓存集成

当多数据源与缓存(如Redis)一起使用时,需要注意缓存键的设计:

@Service
public class CachedUserService {
    
    @Autowired
    private PrimaryUserRepository primaryUserRepository;
    
    @Autowired
    private SecondaryUserRepository secondaryUserRepository;
    
    @Autowired
    private RedisTemplate<String, User> redisTemplate;
    
    private String getCacheKey(String source, Long userId) {
        return String.format("user:%s:%d", source, userId);
    }
    
    @Cacheable(value = "users", key = "#root.target.getCacheKey('primary', #userId)")
    public User getPrimaryUser(Long userId) {
        return primaryUserRepository.findById(userId).orElse(null);
    }
    
    @Cacheable(value = "users", key = "#root.target.getCacheKey('secondary', #userId)")
    public User getSecondaryUser(Long userId) {
        return secondaryUserRepository.findById(userId).orElse(null);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void clearAllUserCache() {
        // 清除所有用户缓存
    }
}

7. 实战案例:电商系统多数据源设计

7.1 场景描述

假设我们有一个电商系统,需要处理以下数据源:

  1. 主库(primary):处理订单、支付等核心业务
  2. 报表库(reporting):处理数据分析、报表生成
  3. 物流库(shipping):处理物流、配送信息

7.2 配置实现

application.yml:

spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/ecommerce_primary
      username: ec_user
      password: ec_password
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        pool-name: PrimaryHikariPool
        maximum-pool-size: 20
    reporting:
      jdbc-url: jdbc:mysql://reporting-host:3306/ecommerce_reporting
      username: reporting_user
      password: reporting_pwd
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        pool-name: ReportingHikariPool
        maximum-pool-size: 15
        read-only: true
    shipping:
      jdbc-url: jdbc:postgresql://shipping-host:5432/ecommerce_shipping
      username: shipping_user
      password: shipping_pwd
      driver-class-name: org.postgresql.Driver
      hikari:
        pool-name: ShippingHikariPool
        maximum-pool-size: 10

数据源配置类:

@Configuration
public class EcommerceDataSourceConfig {
    
    public enum DataSourceType {
        PRIMARY, REPORTING, SHIPPING
    }
    
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.reporting")
    public DataSource reportingDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.shipping")
    public DataSource shippingDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public DataSource dynamicDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.PRIMARY, primaryDataSource());
        targetDataSources.put(DataSourceType.REPORTING, reportingDataSource());
        targetDataSources.put(DataSourceType.SHIPPING, shippingDataSource());
        
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
        
        return dynamicDataSource;
    }
    
    // 动态数据源实现
    private static class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return EcommerceDataSourceContextHolder.getDataSourceType();
        }
    }
}

上下文持有类:

public class EcommerceDataSourceContextHolder {
    private static final ThreadLocal<EcommerceDataSourceConfig.DataSourceType> CONTEXT_HOLDER = 
        new ThreadLocal<>();
    
    public static void setDataSourceType(EcommerceDataSourceConfig.DataSourceType dsType) {
        CONTEXT_HOLDER.set(dsType);
    }
    
    public static EcommerceDataSourceConfig.DataSourceType getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }
    
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

7.3 业务实现示例

订单服务:

@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private OrderReportingRepository orderReportingRepository;
    
    @Transactional
    public void createOrder(Order order) {
        // 使用主数据源
        orderRepository.save(order);
        
        try {
            // 切换到报表数据源
            EcommerceDataSourceContextHolder.setDataSourceType(
                EcommerceDataSourceConfig.DataSourceType.REPORTING);
            
            // 保存订单摘要到报表库
            OrderSummary summary = new OrderSummary();
            summary.setOrderId(order.getId());
            summary.setAmount(order.getAmount());
            summary.setCustomerId(order.getCustomerId());
            summary.setOrderDate(new Date());
            
            orderReportingRepository.save(summary);
        } finally {
            EcommerceDataSourceContextHolder.clearDataSourceType();
        }
    }
    
    @Transactional(readOnly = true)
    public BigDecimal getDailySales() {
        try {
            // 使用报表数据源
            EcommerceDataSourceContextHolder.setDataSourceType(
                EcommerceDataSourceConfig.DataSourceType.REPORTING);
            
            Date today = new Date();
            return orderReportingRepository.sumAmountByDate(today);
        } finally {
            EcommerceDataSourceContextHolder.clearDataSourceType();
        }
    }
}

物流服务:

@Service
public class ShippingService {
    
    @Autowired
    private ShippingRepository shippingRepository;
    
    @Transactional
    public void scheduleShipping(Long orderId, String address) {
        try {
            // 使用物流数据源
            EcommerceDataSourceContextHolder.setDataSourceType(
                EcommerceDataSourceConfig.DataSourceType.SHIPPING);
            
            Shipping shipping = new Shipping();
            shipping.setOrderId(orderId);
            shipping.setAddress(address);
            shipping.setStatus("PENDING");
            shipping.setEstimatedDelivery(new Date(System.currentTimeMillis() + 3 * 24 * 60 * 60 * 1000));
            
            shippingRepository.save(shipping);
        } finally {
            EcommerceDataSourceContextHolder.clearDataSourceType();
        }
    }
}

8. 总结与扩展

8.1 技术选型建议

场景推荐方案理由
简单多数据源,无交叉访问独立配置多个数据源简单直接,易于维护
需要动态切换数据源AbstractRoutingDataSource灵活,可运行时决定数据源
需要强一致性事务JTA(XA)保证ACID,但性能较低
高并发,最终一致性可接受Saga模式高性能,松耦合
读写分离AOP+注解方式透明化,对业务代码侵入小

8.2 未来演进方向

  1. 多数据源与微服务

    • 考虑将部分数据源拆分为独立微服务
    • 使用GraphQL聚合多个数据源的数据
  2. 云原生多数据源

    • 使用Service Mesh管理数据源连接
    • 利用云数据库的Proxy功能简化配置
  3. 数据 federation

    • 使用Apache Calcite等工具实现虚拟数据库
    • 对应用透明地访问多个数据源

关注不关注,你自己决定(但正确的决定只有一个)。

喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clf丶忆笙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值