基于Spring boot輕松實現(xiàn)一個多數(shù)據(jù)源框架
Spring Boot 提供了 Data JPA 的包,允許你使用類似 ORM 的接口連接到 RDMS。它很容易使用和實現(xiàn),只需要在 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);
    }
}最后添加一個數(shù)據(jù)庫連接包,配置數(shù)據(jù)庫連接即可實現(xiàn)與數(shù)據(jù)庫通信。
接下來,我們開始配置多數(shù)據(jù)源連接。
注意:多個數(shù)據(jù)庫應該具有相同的驅動程序。無法連接到不同的數(shù)據(jù)庫,如 MySql 和 Postgres SQL 數(shù)據(jù)庫。數(shù)據(jù)庫必須相同。此外,數(shù)據(jù)庫模式必須相同,不同模式的 2 個數(shù)據(jù)庫無法進行連接。
多數(shù)據(jù)源有哪些應用場景?
1.支持具有相同模式的同一應用程序內的多租戶。
2.動態(tài)模擬多個環(huán)境數(shù)據(jù)庫上的行為 ,而不需要重新啟動應用程序。 例如,你可以動態(tài)連接到開發(fā)數(shù)據(jù)庫或 QA 數(shù)據(jù)庫,而無需重新啟動應用程序。
3.支持多個數(shù)據(jù)庫來模擬各種自動化測試場景。不同數(shù)據(jù)庫可能具有不同的配置和靜態(tài)信息,意味著你可以用一個自動化測試腳本覆蓋多個測試用例。
4.在同一個應用程序中支持多個組織。根據(jù)用戶登錄,可以動態(tài)決定他們的數(shù)據(jù)應進入哪個組織的數(shù)據(jù)庫。
5.一次性為多個數(shù)據(jù)庫插入數(shù)據(jù)。例如,你有一個從腳本創(chuàng)建數(shù)據(jù)的批處理作業(yè),你可以一次性連接到多個數(shù)據(jù)庫,并對所有這些數(shù)據(jù)庫運行腳本,而無需指向不同的應用程序或重新啟動服務器來執(zhí)行此操作。
多數(shù)據(jù)源示意圖如下:

第一步:添加 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>第二步:添加數(shù)據(jù)庫連接配置
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 實例,具有相同的模式但具有不同的數(shù)據(jù)。
第三步:添加多數(shù)據(jù)庫配置。
首先,在 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設置為主數(shù)據(jù)庫
    @Primary
    @Bean(name = "db1DataSource")
    @ConfigurationProperties("app.datasource.db1")
    public DataSource db1DataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    //db2連接數(shù)據(jù)源注入
    @Bean(name = "db2DataSource")
    @ConfigurationProperties("app.datasource.db2")
    public DataSource db2DataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    //db3連接數(shù)據(jù)源注入
    @Bean(name = "db3DataSource")
    @ConfigurationProperties("app.datasource.db3")
    public DataSource db3DataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    //多數(shù)據(jù)源配置
    @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;
    }
}這樣就完成了我們的多數(shù)據(jù)庫配置。
com.sample.client.repositories.dto.entity — 此目錄包含 3 個數(shù)據(jù)庫通用的 JPA 實體。
MultiRoutingDataSource類是我們的實際實現(xiàn),允許我們連接到多個數(shù)據(jù)庫
接下來,我們還需要一個DBContextHolder類來保存數(shù)據(jù)庫引用并在運行時動態(tài)更改數(shù)據(jù)庫。
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 方法用于決定應用程序應該動態(tài)連接到哪個數(shù)據(jù)庫。
好了,我們的配置就完成了。接下來,我們測試下多數(shù)據(jù)源是否生效:
@RestController
@RequestMapping("/client")
public class ClientDataController {
    @Autowired
    private ClientMasterService clientMasterService;
    @GetMapping("/{clientdb}")
    public String findFromDatabase(@PathVariable String clientdbName) {
        return clientMasterService.getClientNames(clientdbName);
    }
}ClientMasterService實現(xiàn)如下:
@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類根據(jù)從 Rest 端點傳入的數(shù)據(jù)庫名稱(db1、db2 或 db3)設置要指向的數(shù)據(jù)庫。
最后,編寫 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; 
}這樣就完成了整個多數(shù)據(jù)源的配置?。?!
總結
如果你有多租戶需求,或者多環(huán)境測試需求等,可以自己嘗試編寫一個多數(shù)據(jù)源框架,也可以引入第三方庫來解決此需求。















 
 
 















 
 
 
 