Java学习手册:Spring 多数据源配置与管理

在实际开发中,有时需要连接多个数据库,例如,一个系统可能需要从不同的数据库中读取和写入数据。Spring 提供了多种方式来配置和管理多数据源,以下将介绍常见的配置和管理方法。

一、多数据源配置

在 Spring 中,可以通过配置多个数据源 Bean,并使用 @Qualifier 注解来区分它们。以下是一个基于 Spring Boot 的多数据源配置示例:

1. 添加依赖

在 Spring Boot 项目的 pom.xml 文件中添加 Spring Data JPA 和数据库驱动的依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
2. 配置文件

application.properties 文件中配置多个数据库连接信息:

properties 复制代码
# 数据源 1 配置
spring.datasource.primary.url=jdbc:mysql://localhost:3306/primarydb?useSSL=false&serverTimezone=UTC
spring.datasource.primary.username=root
spring.datasource.primary.password=password
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

# 数据源 2 配置
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/secondarydb?useSSL=false&serverTimezone=UTC
spring.datasource.secondary.username=root
spring.datasource.secondary.password=password
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
3. 配置类

创建一个配置类,定义多个数据源 Bean:

java 复制代码
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return new BasicDataSource();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return new BasicDataSource();
    }

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(@Qualifier("primaryDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example.primary");
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        em.setJpaProperties(jpaProperties());
        return em;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(@Qualifier("secondaryDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example.secondary");
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        em.setJpaProperties(jpaProperties());
        return em;
    }

    @Bean
    public JpaTransactionManager transactionManager1(@Qualifier("primaryEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory.getObject());
        return transactionManager;
    }

    @Bean
    public JpaTransactionManager transactionManager2(@Qualifier("secondaryEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory.getObject());
        return transactionManager;
    }

    private Map<String, String> jpaProperties() {
        Map<String, String> props = new HashMap<>();
        props.put("hibernate.hbm2ddl.auto", "update");
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        props.put("hibernate.show_sql", "true");
        return props;
    }
}

二、多数据源的切换与使用

在实际的业务逻辑中,可以通过注入多个 EntityManagerDataSource,并使用 @Qualifier 注解来指定具体使用的数据源。

以下是一个简单的示例,演示如何在业务逻辑层切换使用不同的数据源:

java 复制代码
import com.example.primary.domain.User;
import com.example.primary.repository.UserRepository;
import com.example.secondary.domain.Customer;
import com.example.secondary.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private CustomerRepository customerRepository;

    @Transactional(transactionManager = "transactionManager1")
    public void saveUser(User user) {
        userRepository.save(user);
    }

    @Transactional(transactionManager = "transactionManager2")
    public void saveCustomer(Customer customer) {
        customerRepository.save(customer);
    }
}

三、动态数据源切换

在一些复杂的场景下,可能需要动态切换数据源。可以通过实现一个自定义的 AbstractRoutingDataSource 来动态切换数据源。

以下是一个简单的示例:

1. 创建动态数据源上下文
java 复制代码
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}
2. 创建动态数据源
java 复制代码
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}
3. 配置动态数据源
java 复制代码
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import javax.sql.DataSource;
import java.io.IOException;

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() throws IOException {
        return dataSource("primary");
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() throws IOException {
        return dataSource("secondary");
    }

    private DataSource dataSource(String schema) throws IOException {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/" + schema + "?useSSL=false&serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setMaximumPoolSize(5);
        dataSource.setMinimumIdle(1);
        dataSource.setIdleTimeout(30000);
        dataSource.setConnectionTimeout(30000);
        dataSource.setMaxLifetime(600000);

        ResourcePatternResolver resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
        resourceDatabasePopulator.addScript(resourcePatternResolver.getResource("classpath:schema.sql"));
        resourceDatabasePopulator.addScript(resourcePatternResolver.getResource("classpath:test-data.sql"));

        DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
        dataSourceInitializer.setDataSource(dataSource);
        dataSourceInitializer.setDatabasePopulator(resourceDatabasePopulator);
        dataSourceInitializer.setEnabled(true);
        dataSourceInitializer.afterPropertiesSet();

        return dataSource;
    }

    @Bean
    public DynamicDataSource dataSource(
            @Qualifier("primaryDataSource") DataSource primaryDataSource,
            @Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("primary", primaryDataSource);
        dataSourceMap.put("secondary", secondaryDataSource);
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
        return dynamicDataSource;
    }
}

总结

Spring 提供了多种配置和管理多数据源的方式,可以根据项目的实际需求灵活选择。在需要切换数据源的场景下,可以通过配置多个数据源 Bean 并使用 @Qualifier 注解来指定具体使用的数据源。对于更复杂的动态数据源切换场景,可以自定义 AbstractRoutingDataSource 来实现动态切换。掌握多数据源的配置与管理,有助于构建更加灵活、高效的后端应用。

相关推荐
ABCDEEE737 分钟前
民宿管理系统5
java
别催小唐敲代码41 分钟前
解决跨域的4种方法
java·服务器·前端·json
何似在人间5751 小时前
LangChain4j +DeepSeek大模型应用开发——7 项目实战 创建硅谷小鹿
java·人工智能·ai·大模型开发
magic 2451 小时前
深入理解 Spring MVC:DispatcherServlet 与视图解析机制
java·servlet·状态模式·springmvc
小杜-coding1 小时前
黑马点评day02(缓存)
java·spring boot·redis·后端·spring·maven·mybatis
Timmer丿2 小时前
Spring AI开发跃迁指南(第二章:急速上手3——Advisor核心原理、源码讲解及使用实例)
java·人工智能·spring
oliveira-time2 小时前
java单元测试代码
java·windows·单元测试
zfj3212 小时前
用spring-boot-maven-plugin打包成单个jar有哪些缺点&优化方案
java·maven·jar·springboot
柚个朵朵2 小时前
RabbitMQ
java·rabbitmq·java-rabbitmq
程序员曼布3 小时前
ThreadLocal源码深度剖析:内存管理与哈希机制
java·开发语言·哈希算法