基于Spring boot轻松实现一个多数据源框架

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核8GB 50GB
简介: 基于Spring boot轻松实现一个多数据源框架

Spring Boot 提供了 Data JPA 的包,允许你使用类似 ORM 的接口连接到 RDMS。它很容易使用和实现,只需要在 pom.xml 中添加一个条目(如果使用的是 Maven,Gradle 则是在 build.gradle 文件中)。

<dependencies>
        <!-- Spring boot 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency> 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
</dependencies>

在Main Spring Application类中添加 2 个注释:

@SpringBootApplication
@EnableJpaRepositories
@EnableAutoConfiguration
public class SpringMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringMainApplication.class, args);
    }
}

最后添加一个数据库连接包,配置数据库连接即可实现与数据库通信。

接下来,我们开始配置多数据源连接。

注意:多个数据库应该具有相同的驱动程序。无法连接到不同的数据库,如 MySql 和 Postgres SQL 数据库。数据库必须相同。此外,数据库模式必须相同,不同模式的 2 个数据库无法进行连接。

多数据源有哪些应用场景?

1.支持具有相同模式的同一应用程序内的多租户。

2.动态模拟多个环境数据库上的行为 ,而不需要重新启动应用程序。 例如,你可以动态连接到开发数据库或 QA 数据库,而无需重新启动应用程序。

3.支持多个数据库来模拟各种自动化测试场景。不同数据库可能具有不同的配置和静态信息,意味着你可以用一个自动化测试脚本覆盖多个测试用例。

4.在同一个应用程序中支持多个组织。根据用户登录,可以动态决定他们的数据应进入哪个组织的数据库。

5.一次性为多个数据库插入数据。例如,你有一个从脚本创建数据的批处理作业,你可以一次性连接到多个数据库,并对所有这些数据库运行脚本,而无需指向不同的应用程序或重新启动服务器来执行此操作。

多数据源示意图如下:

第一步:添加 pom 依赖

<dependencies>
        <!-- Spring boot dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Swagger dependencies -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- lombok dependency -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
        <!-- Database dependency -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <!-- test dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-parent</artifactId>
                <version>${spring-cloud-dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-gcp-dependencies</artifactId>
                <version>${project.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

第二步:添加数据库连接配置

app.datasource.db1.jdbc-url=jdbc:postgresql://db1.com:5432/dbname1
app.datasource.db1.username=postgres
app.datasource.db1.password=password
app.datasource.db2.jdbc-url=jdbc:postgresql://db2.com:5432/dbname2
app.datasource.db2.username=postgres
app.datasource.db2.password=password
app.datasource.db3.jdbc-url=jdbc:postgresql://db3.com:5432/dbname3
app.datasource.db3.username=postgres
app.datasource.db3.password=password

这是 3 个独立的 PostgresSQL 实例,具有相同的模式但具有不同的数据。

第三步:添加多数据库配置。

首先,在 Spring 应用程序主文件中添加注解:

@SpringBootApplication
@EnableJpaRepositories
@EnableAutoConfiguration
public class MultidatabaseApplication {
    public static void main(String[] args) {
        SpringApplication.run(MultidatabaseApplication.class, args);
    }
}

添加配置类:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "multiEntityManager",
        transactionManagerRef = "multiTransactionManager")
@EntityScan("com.sample.client.repositories.dto.entity")
public class DatabaseConfiguration {
    //添加 JPA 实体路径
    private final String PACKAGE_SCAN = "com.sample.client.repositories.dto.entity";
    // 将db1设置为主数据库
    @Primary
    @Bean(name = "db1DataSource")
    @ConfigurationProperties("app.datasource.db1")
    public DataSource db1DataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    //db2连接数据源注入
    @Bean(name = "db2DataSource")
    @ConfigurationProperties("app.datasource.db2")
    public DataSource db2DataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    //db3连接数据源注入
    @Bean(name = "db3DataSource")
    @ConfigurationProperties("app.datasource.db3")
    public DataSource db3DataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    //多数据源配置
    @Bean(name = "multiRoutingDataSource")
    public DataSource multiRoutingDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(ClientNames.DB1, db1DataSource());
        targetDataSources.put(ClientNames.DB2, db2DataSource());
        targetDataSources.put(ClientNames.DB3, db3DataSource());
        MultiRoutingDataSource multiRoutingDataSource 
            = new MultiRoutingDataSource();
        multiRoutingDataSource.setDefaultTargetDataSource(db1DataSource());
        multiRoutingDataSource.setTargetDataSources(targetDataSources);
        return multiRoutingDataSource;
    }
    //多实体配置代码
    @Bean(name = "multiEntityManager")
    public LocalContainerEntityManagerFactoryBean multiEntityManager() {
        LocalContainerEntityManagerFactoryBean em 
            = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(multiRoutingDataSource());
        em.setPackagesToScan(PACKAGE_SCAN);
        HibernateJpaVendorAdapter vendorAdapter 
            = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(hibernateProperties());
        return em;
    }
    @Bean(name = "multiTransactionManager")
    public PlatformTransactionManager multiTransactionManager() {
        JpaTransactionManager transactionManager
                = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
                multiEntityManager().getObject());
        return transactionManager;
    }
    @Primary
    @Bean(name="entityManagerFactory")
    public LocalSessionFactoryBean dbSessionFactory() {
        LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
        sessionFactoryBean.setDataSource(multiRoutingDataSource());
        sessionFactoryBean.setPackagesToScan(PACKAGE_SCAN);
        sessionFactoryBean.setHibernateProperties(hibernateProperties());
        return sessionFactoryBean;
    }
    //添加 hibernate 属性
    private Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.show_sql", true);
        properties.put("hibernate.format_sql", true);
        properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
        properties.put("hibernate.id.new_generator_mappings", false);
        properties.put("hibernate.jdbc.lob.non_contextual_creation", true);
        return properties;
    }
}

这样就完成了我们的多数据库配置。

com.sample.client.repositories.dto.entity — 此目录包含 3 个数据库通用的 JPA 实体。

MultiRoutingDataSource类是我们的实际实现,允许我们连接到多个数据库

接下来,我们还需要一个DBContextHolder类来保存数据库引用并在运行时动态更改数据库。

public class DBContextHolder {
    private static final ThreadLocal<ClientNames> contextHolder = new ThreadLocal<>();
    public static void setCurrentDb(ClientNames dbType) {
        contextHolder.set(dbType);
    }
    public static ClientNames getCurrentDb() {
        return contextHolder.get();
    }
    public static void clear() {
        contextHolder.remove();
    }
}

ClientNames枚举类如下:

public enum ClientNames {
    DB1, DB2, DB3
}

接下来我们需要对MultiRoutingDataSource进行重写

public class MultiRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.getCurrentDb();
    }
}

determineCurrentLookupKey 方法用于决定应用程序应该动态连接到哪个数据库。

好了,我们的配置就完成了。接下来,我们测试下多数据源是否生效:

@RestController
@RequestMapping("/client")
public class ClientDataController {
    @Autowired
    private ClientMasterService clientMasterService;
    @GetMapping("/{clientdb}")
    public String findFromDatabase(@PathVariable String clientdbName) {
        return clientMasterService.getClientNames(clientdbName);
    }
}

ClientMasterService实现如下:

@Service
public class ClientMasterService {
    @Autowired
    private ClientMasterRepository clientMasterRepository;
    public String getClientNames(String client) {
        switch (client) {
            case "db1":
                DBContextHolder.setCurrentDb(ClientNames.DB1);
                break;
            case "db2":
                DBContextHolder.setCurrentDb(ClientNames.DB2);
                break;
            case "db3":
                DBContextHolder.setCurrentDb(ClientNames.DB3);
                break;
        }
        Entity1 e1 = clientMasterRepository.findByEntity1Name("John Doe");
        if(e1 != null) {
            return "found in database: " + client + " with id " + e1.getId();
        }
        return "found in " + client + " nada!";
    }
}

ClientMasterService使用DBContextHolder类根据从 Rest 端点传入的数据库名称(db1、db2 或 db3)设置要指向的数据库。

最后,编写 JPA Repository 基础代码:

@Repository
public interface ClientMasterRepository extends JpaRepository<Entity1, String> {
    Entity1 findByEntity1Name(String name);
}

Entity1 类如下:

@Entity
@Table(name = "entity1")
@Getter
@Setter
public class Entity1 implements Serializable {
  @Id
  @Column(name = "id", nullable = false)
  private Integer id;
  @Column(name = "entity1Name")
  private String entity1Name; 
}

这样就完成了整个多数据源的配置!!!

总结

如果你有多租户需求,或者多环境测试需求等,可以自己尝试编写一个多数据源框架,也可以引入第三方库来解决此需求。

介绍一款Java+Springboot+Vue的前后端分离项目

这是一款基于SpringBoot+Vue的前后端分离的项目,麻雀虽小,五脏俱全,开箱即用!

JNPF开发平台的前端采用Vue.js,这是一种流行的前端JavaScript框架,用于构建用户界面。Vue.js具有轻量级、可扩展性强和生态系统丰富等特点,被广泛应用于构建单页面应用程序。

后端采用SpringBoot,这是一种基于Java的开源框架,用于简化Spring应用的初始搭建以及开发过程。SpringBoot通过自动配置和约定大于配置的原则,简化了Spring应用的配置和开发。此外,JNPF还采用MyBatis-Plus作为持久层框架,它是一个功能强大的MyBatis扩展,可以大大简化数据库操作的开发。

核心功能:表单引擎、可视化引擎、BI引擎、流程引擎、权限引擎、门户引擎、大屏引擎、接口中心、物联平台

尝试自己开发一个应用!应用地址:https://www.jnpfsoft.com/?csdn

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
3月前
|
安全 Java Ruby
我尝试了所有后端框架 — — 这就是为什么只有 Spring Boot 幸存下来
作者回顾后端开发历程,指出多数框架在生产环境中难堪重负。相比之下,Spring Boot凭借内置安全、稳定扩展、完善生态和企业级支持,成为构建高可用系统的首选,真正经受住了时间与规模的考验。
263 2
|
2月前
|
人工智能 运维 Java
Spring AI Alibaba Admin 开源!以数据为中心的 Agent 开发平台
Spring AI Alibaba Admin 正式发布!一站式实现 Prompt 管理、动态热更新、评测集构建、自动化评估与全链路可观测,助力企业高效构建可信赖的 AI Agent 应用。开源共建,现已上线!
3287 45
|
2月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
4月前
|
XML JSON Java
Spring框架中常见注解的使用规则与最佳实践
本文介绍了Spring框架中常见注解的使用规则与最佳实践,重点对比了URL参数与表单参数的区别,并详细说明了@RequestParam、@PathVariable、@RequestBody等注解的应用场景。同时通过表格和案例分析,帮助开发者正确选择参数绑定方式,避免常见误区,提升代码的可读性与安全性。
|
2月前
|
NoSQL Java 数据库连接
《深入理解Spring》Spring Data——数据访问的统一抽象与极致简化
Spring Data通过Repository抽象和方法名派生查询,简化数据访问层开发,告别冗余CRUD代码。支持JPA、MongoDB、Redis等多种存储,统一编程模型,提升开发效率与架构灵活性,是Java开发者必备利器。(238字)
|
2月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
126 8
|
3月前
|
监控 Kubernetes Cloud Native
Spring Batch 批处理框架技术详解与实践指南
本文档全面介绍 Spring Batch 批处理框架的核心架构、关键组件和实际应用场景。作为 Spring 生态系统中专门处理大规模数据批处理的框架,Spring Batch 为企业级批处理作业提供了可靠的解决方案。本文将深入探讨其作业流程、组件模型、错误处理机制、性能优化策略以及与现代云原生环境的集成方式,帮助开发者构建高效、稳定的批处理系统。
386 1
|
5月前
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
556 0
|
4月前
|
Cloud Native Java API
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
758 0
|
5月前
|
缓存 安全 Java
第五章 Spring框架
Spring IOC(控制反转)通过工厂模式管理对象的创建与生命周期,DI(依赖注入)则让容器自动注入所需对象,降低耦合。常见注解如@Component、@Service用于声明Bean,@Autowired用于注入。Bean默认单例,作用域可通过@Scope配置,如prototype、request等。Spring通过三级缓存解决循环依赖问题,但构造函数循环依赖需用@Lazy延迟加载。AOP通过动态代理实现,用于日志、事务等公共逻辑。事务通过@Transactional实现,需注意异常处理及传播行为。
87 0