1. 程式人生 > >SpringBoot多資料庫連線(mysql+oracle)

SpringBoot多資料庫連線(mysql+oracle)

出於業務需求,有時我們需要在spring boot web應用程式中配置多個數據源並連線到多個數據庫。 使用過Spring Boot框架的小夥伴們,想必都發現了Spring Boot對JPA提供了非常好的支援,在開發過程中可以很簡潔的程式碼輕鬆訪問資料庫,獲取我們想要的資料。 因此在這裡,使用Spring Boot和JPA配置多個數據源的場景。

專案配置

在本文中,主要使用兩個不同的資料庫,分別為:
  • mysql(springboot)【primary,優先搜尋該資料庫】:mysql資料庫,包含User的資訊
  • oracle(springboot): oracle資料庫, 包含Country資訊

專案依賴

為了支援Mysql和Oracle資料庫,我們必須要在pom.xml檔案中新增相應的依賴。

<dependencies>
    <dependency>
       <groupId>com.oracle</groupId>
       <artifactId>ojdbc6</artifactId>
       <version>11.2.0.3.0</version>
       <scope>compile</scope>
    </dependency>
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
       <groupId>javax.persistence</groupId>
       <artifactId>javax.persistence-api</artifactId>
       <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
</dependencies> 

包管理

為了方便程式碼的開發,我們將mysql和oracle分開放在兩個不同的package下面,具體的包結構如下:

將不同的模型分開放入mysql和oracle包目錄下,需要注意的是,mysql和oracle為兩個不同的資料庫,所以兩個資料庫中可能存在某個表名稱一致的場景。該場景下,會優先匹配primary的資料庫,如果該資料庫down了,才會匹配另外一張表。所以,如果想要兩張表都正常使用,建議使用不同的Entity名稱。

資料庫連線配置

我們在屬性檔案application.properties中分別配置兩個單獨的jdbc連線,將所有關聯的Entity類和Repository對映到兩個不同的包中。

## jdbc-primary
spring.datasource.url=jdbc:mysql://localhost:33306/springboot?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false
spring.datasource.username=springboot
spring.datasource.password=123456
spring.ds_mysql.driverClassName=com.mysql.jdbc.Driver

## jdbc-second
spring.second.datasource.url=jdbc:oracle:thin:@localhost:1909/xxx.xxx.com
spring.second.datasource.userName=springboot
spring.second.datasource.password=123456
spring.second.datasource.driver-class-name=oracle.jdbc.OracleDriver

## jpa
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
spring.jpa.properties.hibernate.jdbc.fetch_size=500
spring.jpa.properties.hibernate.jdbc.batch_size=100

資料來源配置

需要注意的是,在配置多個數據源期間,必須將其中一個數據源標記為primary,否則Spring Boot會檢測到多個型別的資料來源,從而無法正常啟動。

定義Data Source的Bean

想要建立Data Source,我們必須先例項化org.springframework.boot.autoconfigure.jdbc.DataSourceProperties類,載入application.properties檔案中配置的資料庫連線資訊,並通過DataSourceProperties物件的初始化builder方法建立一個javax.sql.DataSource物件。 primary Data Source

@Primary
@Bean(name = "mysqlDataSourceProperties")
@ConfigurationProperties("spring.datasource")
public DataSourceProperties dataSourceProperties() {
    return new DataSourceProperties();
}

@Primary
@Bean(name = "mysqlDataSource")
@ConfigurationProperties("spring.datasource.configuration")
public DataSource dataSource (@Qualifier("mysqlDataSourceProperties") DataSourceProperties mysqlDataSourceProperties) {
    return mysqlDataSourceProperties.initializeDataSourceBuilder()
            .type(HikariDataSource.class)
            .build();
}
Secondary Data Source
@Bean(name = "oracleDataSourceProperties")
@ConfigurationProperties("spring.second.datasource")
public DataSourceProperties dataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("spring.second.datasource.configuration")
public DataSource oracleDataSource(@Qualifier("oracleDataSourceProperties") DataSourceProperties oracleDataSourceProperties) {
    return oracleDataSourceProperties.initializeDataSourceBuilder()
            .type(HikariDataSource.class)
            .build();
}

我們使用@Qualifier註解,自動關聯指定的DataSourceProperties.

定義實體類管理工廠的Bean 上面說了,應用程式使用Spring Data JPA的repository介面將我們從實體管理器(Entity Manager)中抽象出來,從而進行資料的訪問。這裡,我們使用org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean這個Bean來建立EM例項,後面利用這個EM例項與JPA entities進行互動。 由於我們這裡有兩個資料來源,所以我要為每個資料來源單獨建立一個EntityManagerFactory。 Primary Entity Manager Factory
@Primary
@Bean(name = "mysqlEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
        EntityManagerFactoryBuilder builder, @Qualifier("mysqlDataSource") DataSource mysqlDataSource) {
    return builder.dataSource(mysqlDataSource)
            .packages("com.example.demo.model.mysql")
            .persistenceUnit("mysql")
            .build();
}
Secondary Entity Manager Factory

@Bean(name = "oracleEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean oracleEntityManagerFactory(
        EntityManagerFactoryBuilder builder, @Qualifier("oracleDataSource") DataSource oracleDataSource) {
    return builder.dataSource(oracleDataSource)
            .packages("com.example.demo.model.oracle")
            .persistenceUnit("oracle")
            .build();
}
我們使用@Qualifie註解,自動將DataSource關聯到對應的EntityManangerFactory中。 在這裡我們可以分別配置實體類管理工廠所管理的packages,為了方便開發和閱讀,分別將mysql和oracle關聯的實體類放在對應的目錄下。

事務管理

我們為每個資料庫建立一個JPA事務管理器。 檢視原始碼我們可以發現JPA事務管理器需要EntityManangerFactory作為入參,所以利用上述定義的EntityMangerFactory分別生成對應的JPA事物管理器。 原始碼:

public JpaTransactionManager(EntityManagerFactory emf) {
    this();
    this.entityManagerFactory = emf;
    this.afterPropertiesSet();
}
Primary transaction manager

@Primary
@Bean(name = "mysqlTransactionManager")
public PlatformTransactionManager mysqlTransactionManager(final @Qualifier("mysqlEntityManagerFactory")
                                                                 LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory) {
    return new JpaTransactionManager(mysqlEntityManagerFactory.getObject());
}
Secondary transaction manager

@Bean(name = "oracleTransactionManager")
public PlatformTransactionManager oracleTransactionManager(
        final @Qualifier("oracleEntityManagerFactory")
                LocalContainerEntityManagerFactoryBean oracleEntityManagerFactory) {
    return new JpaTransactionManager(oracleEntityManagerFactory.getObject());
}

JPA Repository配置

由於我們使用了兩個不同的資料來源,所以我們必須使用@EnableJpaRepositories註解為每個資料來源提供特定的資訊。 進入該註解原始碼,我們可以發現預設值如下:

/**
 * Annotation to enable JPA repositories. Will scan the package of the annotated configuration class for Spring Data
 * repositories by default.
 *
 * @author Oliver Gierke
 * @author Thomas Darimont
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(JpaRepositoriesRegistrar.class)
public @interface EnableJpaRepositories {
    /**
     * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this
     * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
     */
    String[] basePackages() default {};
    
    /**
     * Configures the name of the {@link EntityManagerFactory} bean definition to be used to create repositories
     * discovered through this annotation. Defaults to {@code entityManagerFactory}.
     *
     * @return
     */
    String entityManagerFactoryRef() default "entityManagerFactory";
    
    /**
     * Configures the name of the {@link PlatformTransactionManager} bean definition to be used to create repositories
     * discovered through this annotation. Defaults to {@code transactionManager}.
     *
     * @return
     */
    String transactionManagerRef() default "transactionManager";
}
這裡僅列了一些我們關心的方法。 從原始碼我中我們可以看見,
  • basePackages: 使用此欄位設定Repository的基本包,必須指向軟體包中repository所在目錄。
  • entityManagerFactoryRef:使用此欄位引用預設或自定義的Entity Manager Factory, 這裡通過Bean的名稱進行指定, 預設Bean為entityManagerFactory。
  • transactionManagerRef:使用此欄位引用預設或自定義的事務管理器,這裡通過Bean的名稱進行指定,預設Bean為transactionManager。
通過上面的內容,我們為兩個不同的資料來源分別定義了不同的名稱,所以我們需要在這裡分別將其注入容器中。 Primary

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"com.example.demo.repository.mysql"},
        entityManagerFactoryRef = "mysqlEntityManagerFactory", transactionManagerRef = "mysqlTransactionManager")
public class MysqlDataSourceConfiguration {
    ...
}
secondary

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.example.demo.repository.oracle",
        entityManagerFactoryRef = "oracleEntityManagerFactory", transactionManagerRef = "oracleTransactionManager")
public class OracleDataSourceConfiguration {
    ...
}

完整的配置檔案

primary

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"com.example.demo.repository.mysql"},
        entityManagerFactoryRef = "mysqlEntityManagerFactory", transactionManagerRef = "mysqlTransactionManager")
public class MysqlDataSourceConfiguration {

    @Primary
    @Bean(name = "mysqlDataSourceProperties")
    @ConfigurationProperties("spring.datasource")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "mysqlDataSource")
    @ConfigurationProperties("spring.datasource.configuration")
    public DataSource dataSource (@Qualifier("mysqlDataSourceProperties") DataSourceProperties mysqlDataSourceProperties) {
        return mysqlDataSourceProperties.initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
    }

    @Primary
    @Bean(name = "mysqlEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("mysqlDataSource") DataSource mysqlDataSource) {
        return builder.dataSource(mysqlDataSource)
                .packages("com.example.demo.model.mysql")
                .persistenceUnit("mysql")
                .build();
    }

    @Primary
    @Bean(name = "mysqlTransactionManager")
    public PlatformTransactionManager transactionManager(final @Qualifier("mysqlEntityManagerFactory")
                                                                     LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory) {
        return new JpaTransactionManager(mysqlEntityManagerFactory.getObject());
    }
}
secondary

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.example.demo.repository.oracle",
        entityManagerFactoryRef = "oracleEntityManagerFactory", transactionManagerRef = "oracleTransactionManager")
public class OracleDataSourceConfiguration {
    @Bean(name = "oracleDataSourceProperties")
    @ConfigurationProperties("spring.second.datasource")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("spring.second.datasource.configuration")
    public DataSource oracleDataSource(@Qualifier("oracleDataSourceProperties") DataSourceProperties oracleDataSourceProperties) {
        return oracleDataSourceProperties.initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
    }

    @Bean(name = "oracleEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean oracleEntityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("oracleDataSource") DataSource oracleDataSource) {
        return builder.dataSource(oracleDataSource)
                .packages("com.example.demo.model.oracle")
                .persistenceUnit("oracle")
                .build();
    }

    @Bean
    public PlatformTransactionManager oracleTransactionManager(
            final @Qualifier("oracleEntityManagerFactory")
                    LocalContainerEntityManagerFactoryBean oracleEntityManagerFactory) {
        return new JpaTransactionManager(oracleEntityManagerFactory.getObject());
    }
}

總結

當僅有一個數據源時,Spring Boot會預設自動配置好,但是如果使用多個數據源時,需要進行一些自定義的配置,以上便是全部的配置。 總體感覺並不是特別複雜,耐心理解下,還是很容易理解的。 如有錯漏之處,還望各位大佬們指正。  
 作者:吳家二少 部落格地址:https://www.cnblogs.com/cloudman-open/ 本文歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線