MeteorCat / JPA多数据源配置

Created Thu, 31 Oct 2024 22:13:21 +0800 Modified Wed, 29 Oct 2025 23:25:00 +0800
1406 Words

JPA多数据源配置

JavaJPA ORM 框架默认单数据处理, 也就是假设数据库当中有 admin|app|business 这个不同区域数据库的时候就需要自己处理好相关多数据源配置.

这种情况是十分常见的, 在高并发负载的情况下业务会拆分到多个数据源的数据库来暴露给内网服务机连接

首先需要编写 application.properties 配置文件, 改写原来的配置:

# 其他略
#================================================================
# Admin库, 注意这里采用 jdbc-url 而不是单纯的 url 配置, 并且采用 MariaDB 驱动而非 MySQL
spring.datasource.admin.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.admin.jdbc-url=jdbc:mariadb://127.0.0.1:3306/admin?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=UTC
spring.datasource.admin.username=admin
spring.datasource.admin.password=admin
# App库
spring.datasource.app.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.app.jdbc-url=jdbc:mariadb://127.0.0.1:3306/app?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=UTC
spring.datasource.app.username=app
spring.datasource.app.password=app
# hikari配置
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=2
spring.datasource.hikari.connection-test-query=SELECT 1
#================================================================

需要说明如果采用多数据源配置, 应该采用 spring.datasource.数据源KEY标识.driver-class-name 格式来处理, 另外需要注意 spring.datasource.数据源KEY标识.jdbc-url 这个配置采用的是 jdbc-url 而不是 url.

现在就是编写将这些配置注入到 IOC 容器当中配置:

package com.meteorcat.sdk.orange.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * 数据库多库配置, 让其自动装载配置参数
 */
@Configuration
public class DataSourceConfig {

    /**
     * 主库配置: Admin后台库,
     * 注意:
     * 1.必须要利用 @Primary 指定默认数据库主库, 其他配置默认都为从库不需要配置该注解
     * 2.@Bean最好手动声明指定全局配置数据源名称,也就是指定 name 配置
     * 3.@ConfigurationProperties 就是获取到配置文件当中的 spring.datasource.admin 配置项
     * 4.@Qualifier 用来指定声明防止其他注册的 Bean 冲突
     */
    @Primary
    @Bean(name = "AdminDataSource")
    @Qualifier("AdminDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.admin")
    public DataSource adminDataSource() {
        return DataSourceBuilder.create().build();
    }


    /**
     * 次库配置: App应用库
     * 说明同上, 次库不需要指定 Primary
     */
    @Bean(name = "AppDataSource")
    @Qualifier("AppDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.app")
    public DataSource appDataSource() {
        return DataSourceBuilder.create().build();
    }


    /**
     * 后续如果扩展其他子数据源也能继续添加
     */
    @Bean(name = "BusinessDataSource")
    @Qualifier("BusinessDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.business")
    public DataSource appDataSource() {
        return DataSourceBuilder.create().build();
    }
}

这里之后就是添加 Jpa 的数据源配置, 首先是 Admin 数据源(后台管理)当中配置:

package com.meteorcat.sdk.orange.config.db;


import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
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.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

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

/**
 * Admin库检索, 配置检索 com.meteorcat.sdk.orange.repository.admin 包内部仓库
 * entityManagerFactoryRef 是声明注入依赖的 Bean, 也就是需要在内部声明同名返回 Bean, transactionManagerRef 同理
 * basePackages 就是代表主要扫描的包内部 entity 和 repository 类文件
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManagerFactoryByAdmin",
        transactionManagerRef = "transactionManagerByAdmin",
        basePackages = {"com.meteorcat.sdk.orange.repository.admin"})
public class AdminDataSourceConfig {


    /**
     * 加载全局 AdminDataSource 的 Bean
     */
    private final DataSource adminDatasource;


    /**
     * 加载全局 JpaProperties 属性参数
     */
    private final JpaProperties jpaProperties;


    /**
     * 加载全局 Hibernate 属性参数
     */
    private final HibernateProperties hibernateProperties;


    /**
     * 采用构造方法输入 Bean, 官方推荐方式 @Qualifier 指定 Bean 对象名为 AdminDataSource
     *
     * @param adminDatasource     DataSourceConfig:AdminDataSource
     * @param jpaProperties       Jpa配置
     * @param hibernateProperties hibernate配置
     */
    public AdminDataSourceConfig(@Qualifier("AdminDataSource") DataSource adminDatasource, JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
        this.adminDatasource = adminDatasource;
        this.jpaProperties = jpaProperties;
        this.hibernateProperties = hibernateProperties;
    }

    /**
     * 获取JPA设置
     */
    private Map<String, Object> getVendorProperties() {
        return hibernateProperties.determineHibernateProperties(
                jpaProperties.getProperties(), new HibernateSettings());
    }


    /**
     * Entity管理器, 加载设置 EntityManager
     *
     * @param builder 构建器
     * @return EntityManager
     */
    @Primary
    @Bean(name = "entityManagerByAdmin")
    public EntityManager entityManagerByAdmin(EntityManagerFactoryBuilder builder) {
        return Objects.requireNonNull(entityManagerFactoryByAdmin(builder).getObject()).createEntityManager();
    }


    /**
     * 设置实体类所在位置
     * 注意: 就是让其扫描出 com.meteorcat.sdk.orange.repository.admin 包内部的 Entity 和 Repository
     *
     * @param builder 构建器
     * @return LocalContainerEntityManagerFactoryBean
     */
    @Primary
    @Bean(name = "entityManagerFactoryByAdmin")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryByAdmin(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(adminDatasource)
                .packages("com.meteorcat.sdk.orange.repository.admin")
                .persistenceUnit("AdminPersistenceUnit")
                .properties(getVendorProperties())
                .build();
    }


    /**
     * 设置事务管理对象
     *
     * @param builder 构建器
     * @return PlatformTransactionManager
     */
    @Primary
    @Bean(name = "transactionManagerByAdmin")
    public PlatformTransactionManager transactionManagerByAdmin(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactoryByAdmin(builder).getObject()));
    }
}

需要主数据库必须配置存在且唯一声明 @Primary, 这才能被 JPA 识别为默认数据库配置, 后续就是从库配置:

package com.meteorcat.sdk.orange.config.db;


import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
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.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

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

/**
 * App库检索, 配置检索 com.meteorcat.sdk.orange.repository.app 包内部仓库
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManagerFactoryByApp",
        transactionManagerRef = "transactionManagerByApp",
        basePackages = {"com.meteorcat.sdk.orange.repository.app"})
public class AppDataSourceConfig {

    private final DataSource appDatasource;

    private final JpaProperties jpaProperties;

    private final HibernateProperties hibernateProperties;

    public AppDataSourceConfig(@Qualifier("AppDataSource") DataSource appDatasource, JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
        this.appDatasource = appDatasource;
        this.jpaProperties = jpaProperties;
        this.hibernateProperties = hibernateProperties;
    }

    /**
     * 获取JPA设置
     */
    private Map<String, Object> getVendorProperties() {
        return hibernateProperties.determineHibernateProperties(
                jpaProperties.getProperties(), new HibernateSettings());
    }


    @Bean(name = "entityManagerByApp")
    public EntityManager entityManagerByApp(EntityManagerFactoryBuilder builder) {
        return Objects.requireNonNull(entityManagerFactoryByApp(builder).getObject()).createEntityManager();
    }

    /**
     * 设置实体类所在位置
     */
    @Bean(name = "entityManagerFactoryByApp")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryByApp(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(appDatasource)
                .packages("com.meteorcat.sdk.orange.repository.app")
                .persistenceUnit("AppPersistenceUnit")
                .properties(getVendorProperties())
                .build();
    }


    @Bean(name = "transactionManagerByApp")
    public PlatformTransactionManager transactionManagerByApp(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactoryByApp(builder).getObject()));
    }
}

基本和 admin 配置一致, 但是不需要添加 @Primary 注解, 这要声明的 Jpa 都是隔离 repository.adminrepository.app 层级对象被不同数据源处理.

这样的目录配置:
java包 --------- repository
                    |
                    |
                    -------------- admin(AdminJPA数据源的目录)
                    |
                    |
                    -------------- app(AppJPA数据源的目录)

按照 Jpa 文件放置在不同目录之中会切换到不同数据源, 后续就是和正常 JPA 文件一样编写处理就行了.