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.java

定義實體類管理工廠的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會默認自動配置好,可是若是使用多個數據源時,須要進行一些自定義的配置,以上即是所有的配置。
整體感受並非特別複雜,耐心理解下,仍是很容易理解的。
若有錯漏之處,還望各位大佬們指正。
 
 做者:吳家二少
本文歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接 
相關文章
相關標籤/搜索