java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot多数据源配置

SpringBoot进行多数据源配置的详细步骤

作者:墨鸦_Cormorant

多数据源是指在一个应用程序中同时连接和使用多个数据库的能力,这篇文章主要为大家介绍了SpringBoot进行多数据源配置的详细步骤,有需要的小伙伴可以了解下

多数据源核心概念

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

多数据源实现示例

多数据源的配置文件以及配置类

application.yml 配置示例

spring:
  datasource:
      jdbc-url: jdbc:mysql://localhost:3306/db1 # 主数据源
      username: root
      password: root123
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        pool-name: PrimaryHikariPool
        # 最大连接数 
        maximum-pool-size: 20
        # 最小空闲连接
        minimum-idle: 5
        # 空闲连接超时时间(ms)
        idle-timeout: 30000
        # 连接最大生命周期(ms)
        max-lifetime: 1800000
        # 获取连接超时时间(ms)
        connection-timeout: 30000
        connection-test-query: SELECT 1
  second-datasource:
      jdbc-url: jdbc:mysql://localhost:3306/db2 # 主数据源
      username: root
      password: root123
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        pool-name: SecondHikariPool
        maximum-pool-size: 20
        minimum-idle: 5
        idle-timeout: 30000
        max-lifetime: 1800000
        connection-timeout: 30000
        connection-test-query: SELECT 1

多数据源配置类

import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class DbConfig {

    @Bean("db1DataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSourceProperties db1DataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "db1DataSource")
    public DataSource dataSource() {
        return db1DataSourceProperties().initializeDataSourceBuilder().build();
    }

    
    @Bean("db2DataSourceProperties")
    @ConfigurationProperties(prefix = "spring.second-datasource")
    public DataSourceProperties db2DataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "db2DataSource")
    public DataSource db2DataSource() {
        return db2DataSourceProperties().initializeDataSourceBuilder().build();
    }
}

禁用默认数据源

多数据源时需在主类排除自动配置

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) 
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

JPA 多数据源配置

主数据源 JAP 配置

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Objects;

@Configuration
// 启用 Spring 的事务管理功能,允许使用 @Transactional 注解来管理事务
@EnableTransactionManagement
// 启用 JPA 仓库的自动扫描和注册功能
@EnableJpaRepositories(
        // 指定要扫描的 JPA 仓库接口所在的包路径
        basePackages = "com.example.db1",
        // 指定使用的实体管理器工厂的 Bean 名称
        entityManagerFactoryRef = "db1EntityManagerFactory",
        // 指定使用的事务管理器的 Bean 名称
        transactionManagerRef = "db1TransactionManager"
)
public class Db1JpaConfig {
    /**
     * 创建实体管理器工厂的 Bean,并将其标记为主要的实体管理器工厂 Bean
     */
    @Bean(name = "db1EntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            @Qualifier("db1DataSource")DataSource dataSource,
            JpaProperties jpaProperties) {
        return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), 
                                               new HashMap<>(), null)
                // 设置数据源
                .dataSource(dataSource)
                // 指定要扫描的实体类所在的包路径
                .packages("com.example.db1")
                // 设置持久化单元的名称
                .persistenceUnit("db1")
                // 设置 JPA 的属性
                .properties(jpaProperties.getProperties())
                .build();
    }

    /**
     * 创建事务管理器的 Bean,并将其标记为主要的事务管理器 Bean
     */
    @Bean(name = "db1TransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("db1EntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactory.getObject()));
    }

    /**
     * QueryDSL的核心组件
     */
    @Bean(name = "db1JPAQueryFactory")
    public JPAQueryFactory db1JPAQueryFactory(
            @Qualifier("db1EntityManagerFactory") EntityManager entityManager) {
        return new JPAQueryFactory(entityManager);
    }
}

从数据源 JAP 集成配置(略)

MyBatis 多数据源配置

主数据源 MyBatis 配置

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;

@Configuration
// 此注解用于指定 MyBatis Mapper 接口的扫描范围和对应的 SqlSessionFactory 引用
@MapperScan(
        // 指定要扫描的 Mapper 接口所在的基础包路径
        basePackages = "com.example.mapper.db1",
        // 配置使用的 SqlSessionFactory Bean 的名称
        sqlSessionFactoryRef = "db1SqlSessionFactory"
)
public class Db1MyBatisConfig {

    /**
     * 创建 SqlSessionFactory Bean
     */
    @Bean("db1SqlSessionFactory")
    public SqlSessionFactory db1SqlSessionFactory(
            @Qualifier("db1DataSource") DataSource dataSource) throws Exception {
        // 创建 SqlSessionFactoryBean 实例,用于创建 SqlSessionFactory
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 设置 SqlSessionFactory 使用的数据源
        sessionFactory.setDataSource(dataSource);
        // 设置 Mapper XML 文件的位置,使用 PathMatchingResourcePatternResolver 来查找匹配的资源
        sessionFactory.setMapperLocations(
                new PathMatchingResourcePatternResolver()
                        .getResources("classpath:mapper/db1/*.xml"));
        // 获取并返回 SqlSessionFactory 实例
        return sessionFactory.getObject();
    }

    /**
     * 创建 SqlSessionTemplate Bean
     */
    @Bean("db1SqlSessionTemplate")
    public SqlSessionTemplate db1SqlSessionTemplate(
            @Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        // 创建并返回 SqlSessionTemplate 实例,用于简化 MyBatis 的操作
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    /**
     * 创建事务管理器的 Bean,并将其标记为主要的事务管理器 Bean
     */
    @Bean("db1TransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("db1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

从数据源 MyBatis 配置(略)

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

单数据源事务

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

@Service
public class AccountService {

    @Transactional  // 使用默认事务管理器
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // do some thing ...
    }
}

多数据源事务挑战

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

解决方案对比:

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

事务管理器:DataSourceTransactionManager 和 JpaTransactionManager

DataSourceTransactionManager 和 JpaTransactionManager 是 Spring 框架中针对不同持久层技术的事务管理器。

技术栈适配差异

1.DataSourceTransactionManager

适用场景:纯 JDBC、MyBatis、JdbcTemplate 等基于原生 SQL 的数据访问技术

事务控制对象:直接管理 java.sql.Connection ,通过数据库连接实现事务

局限性:

2.JpaTransactionManager

适用场景:JPA 规范实现(如 Hibernate、EclipseLink)

事务控制对象:管理 JPA EntityManager,通过其底层连接协调事务

核心优势:

3.混合技术栈的特殊情况

混合技术栈需严格隔离事务管理器,并考虑分布式事务需求

JPA操作使用JpaTransactionManager,MyBatis操作使用DataSourceTransactionManager

跨数据源事务需引入分布式事务(如Atomikos),否则不同数据源的事务无法保证原子性

若一个 Service 方法同时使用 JPA和 Mybatis(未验证):

事务同步机制对比

特性DataSourceTransactionManagerJpaTransactionManager
连接资源管理直接管理 Connection通过 EntityManager 间接管理连接
跨技术兼容性仅限 JDBC 系技术支持 JPA 及其混合场景(如 JPA+JDBC)
高级 ORM 功能支持不支持(如延迟加载)完整支持 JPA 特性
配置复杂度简单(仅需 DataSource)需额外配置 EntityManagerFactory

多数据源事务使用

事务配置详见上文

多数据源事务使用示例

import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountService {
    
    @Transactional(transactionManager = "db1TransactionManager")  // 指定事务管理器
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // do some thing ...
    }
}

基于 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();
    }
}

动态数据源配置

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DynamicDataSourceConfig {

    /**
     * 创建动态数据源 Bean,并将其设置为主要的数据源 Bean
     */
    @Bean
    @Primary
    public DataSource dynamicDataSource(
            @Qualifier("db1DataSource") DataSource db1DataSource,
            @Qualifier("db2DataSource") DataSource db2DataSource) {
        // 用于存储目标数据源的映射,键为数据源标识,值为数据源实例
        Map<Object, Object> targetDataSources = new HashMap<>();
        // 将主数据源添加到目标数据源映射中,使用自定义的主数据源标识
        targetDataSources.put(DynamicDataSourceContextHolder.PRIMARY_DS, db1DataSource);
        // 将从数据源添加到目标数据源映射中,使用自定义的从数据源标识
        targetDataSources.put(DynamicDataSourceContextHolder.SECONDARY_DS, db2DataSource);

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

        return dynamicDataSource;
    }

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

基于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  // 执行该注解标记的方法时,前后都会执行ReadWriteDataSourceAspect切面类方法
    @Transactional
    public Product getProduct(Long id) {
        // 使用从数据源(读)
        return productRepository.findById(id).orElse(null);
    }
    
    @ReadOnly
    @Transactional
    public List<Product> listProducts() {
        // 使用从数据源(读)
        return productRepository.findAll();
    }
}

常见问题与解决方案

典型问题排查表

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

数据源切换失败案例分析

问题描述:

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

原因分析:

解决方案:

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

多数据源与缓存集成

当多数据源与缓存(如 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() {
        // 清除所有用户缓存
    }
}

总结与扩展

技术选型建议

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

以上就是SpringBoot进行多数据源配置的详细步骤的详细内容,更多关于SpringBoot多数据源配置的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文