第 3-6 課:Spring Data JPA 多數據源的使用

項目中使用多個數據源在以往工作中比較常見,微服務架構中不建議一個項目使用多個數據源。在微服務架構下,一個微服務擁有自己獨立的一個數據庫,如果此微服務要使用其他數據庫的數據,需要調用對應庫的微服務接口來調用,而不是在一個項目中連接使用多個數據庫,這樣微服務更獨立、更容易水平擴展。

雖然在微服務架構下,不提倡一個項目擁有多個數據源,但在 Spring Boot 體系中,項目實現多數據源調用卻是一件很容易的事情,本節課將介紹 Spring Data JPA 多數據源的使用。

Spring Data JPA 使用多數據源的整體思路是,配置不同的數據源,在啓動時分別加載多個數據源配置,並且注入到不同的 repository 中。這樣不同的 repository 包就有不同的數據源,使用時注入對應包下的 repository,就會使用對應數據源的操作。

對照前兩課的示例項目,本課內容將會對項目結構有所調整,如下:

其中:

  • config 啓動時加載、配置多數據源;
  • model 存放數據操作的實體類;
  • repository 目錄下有兩個包路徑 test1 和 test2 ,分別代表兩個不同數據源下的倉庫,這兩個包下的 repository 可以相同也可以不同。

下面演示一下項目。

多數據源的支持

配置 Spring Data JPA 對多數據源的使用,一般分爲以下幾步:

  • 創建數據庫 test1 和 test2
  • 配置多數據源
  • 不同源的 repository 放入不同包路徑
  • 聲明不同的包路徑下使用不同的數據源、事務支持
  • 不同的包路徑下創建對應的 repository
  • 測試使用

上面的一些步驟我們在前面兩課中已經講過了,這裏只補充不同的內容。

配置兩個數據源:

spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

#SQL 輸出
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.hbm2ddl.auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
#format 一下 SQL 進行輸出
spring.jpa.properties.hibernate.format_sql=true

設置將項目中的 SQL 格式化後打印出來,方便在開發過程中調試跟蹤。

創建 DataSourceConfig 添加 @Configuration 註解,在項目啓動時運行初始化數據庫資源。

@Configuration
public class DataSourceConfig {
}

在 DataSourceConfig 類中加載配置文件,利用 ConfigurationProperties 自動裝配的特性加載兩個數據源。

加載第一個數據源,數據源配置以 spring.datasource.primary 開頭,注意當有多個數據源時,需要將其中一個標註爲 @Primary,作爲默認的數據源使用。

@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource firstDataSource() {
    return DataSourceBuilder.create().build();
}

加載第二個數據源,數據源配置以 spring.datasource.secondary 爲開頭。

@Bean(name = "secondaryDataSource")
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondDataSource() {
    return DataSourceBuilder.create().build();
}

加載 JPA 的相關配置信息,JpaProperties 是 JPA 的一些屬性配置信息,構建 LocalEntityManagerFactoryBean 需要參數信息注入到方法中。

@Autowired
private JpaProperties jpaProperties;
@Autowired
private HibernateProperties hibernateProperties;

@Bean(name = "vendorProperties")
public Map<String, Object> getVendorProperties() {
    return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
}

第一個數據源的加載配置過程

首先來看第一個數據源的加載配置過程,創建 PrimaryConfig 類,將上面創建好的第一個數據源注入到類中,添加 @Configuration 和 @EnableTransactionManagement 註解,第一個代表啓動時加載,第二個註解表示啓用事務,同時將第一個數據源和 JPA 配置信息注入到類中。

@Configuration
@EnableTransactionManagement
public class PrimaryConfig {
    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;
    @Autowired
    @Qualifier("vendorProperties")
    private Map<String, Object> vendorProperties;
}

LocalEntityManagerFactoryBean 負責創建一個適合於僅使用 JPA 進行數據訪問的環境的 EntityManager,構建的時候需要指明提示實體類的包路徑、數據源和 JPA 配置信息。

@Bean(name = "entityManagerFactoryPrimary")
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
    return builder
            .dataSource(primaryDataSource)
            .properties(vendorProperties)
            .packages("com.neo.model") //設置實體類所在位置
            .persistenceUnit("primaryPersistenceUnit")
            .build();
}

利用上面的 entityManagerFactoryPrimary() 方法構建好最終的 EntityManager。

@Bean(name = "entityManagerPrimary")
@Primary
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
    return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
}

EntityManager 是 JPA 中用於增、刪、改、查的接口,它的作用相當於一座橋樑,連接內存中的 Java 對象和數據庫的數據存儲。使用 EntityManager 中的相關接口對數據庫實體進行操作的時候, EntityManager 會跟蹤實體對象的狀態,並決定在特定時刻將對實體的操作映射到數據庫操作上面。

同時給數據源添加上 JPA 事務。

@Bean(name = "transactionManagerPrimary")
@Primary
PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
    return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
}

最後一步最爲關鍵,將我們在類中配置好的 EntityManager 和事務信息注入到對應數據源的 repository 目錄下,這樣此目錄下的 repository 就會擁有對應數據源和事務的信息。

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactoryPrimary",
        transactionManagerRef="transactionManagerPrimary",
        basePackages= { "com.neo.repository.test1" })//設置dao(repo)所在位置
public class PrimaryConfig {}

其中,basePackages 支持設置多個包路徑,例如,basePackages= { "com.neo.repository.test1","com.neo.repository.test3" }

到此第一個數據源配置完成了。

第二個數據源的加載配置過程

第二個數據源配置和第一個數據源配置類似,只是方法上去掉了註解:@Primary,第二個數據源數據源加載配置類 SecondaryConfig 完整代碼如下:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactorySecondary",
        transactionManagerRef="transactionManagerSecondary",
        basePackages= { "com.neo.repository.test2" })
public class SecondaryConfig {
    @Autowired
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;

    @Autowired
    @Qualifier("vendorProperties")
    private Map<String, Object> vendorProperties;

    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(secondaryDataSource)
                .properties(vendorProperties)
                .packages("com.neo.model")
                .persistenceUnit("secondaryPersistenceUnit")
                .build();
    }

    @Bean(name = "entityManagerSecondary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactorySecondary(builder).getObject().createEntityManager();
    }

    @Bean(name = "transactionManagerSecondary")
    PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
    }
}

到此多數據源的配置就完成了,項目中使用哪個數據源的操作,就注入對應包下的 repository 進行操作即可,接下來我們對上面配置好的數據源進行測試。

創建 UserRepositoryTests 測試類,將兩個包下的 repository 都注入到測試類中:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTests {
    @Resource
    private UserTest1Repository userTest1Repository;
    @Resource
    private UserTest2Repository userTest2Repository;
}

首先測試兩個數據庫中都存入數據,數據源1插入 2 條用戶信息,數據源2插入 1 條用戶信息。

@Test
public void testSave() throws Exception {
    Date date = new Date();
    DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
    String formattedDate = dateFormat.format(date);

    userTest1Repository.save(new User("aa", "aa123456","[email protected]", "aa",  formattedDate));
    userTest1Repository.save(new User("bb", "bb123456","[email protected]", "bb",  formattedDate));
    userTest2Repository.save(new User("cc", "cc123456","[email protected]", "cc",  formattedDate));
}

執行完測試用例後查看數據庫,發現 test1 庫有兩條數據,test2 有一條,證明兩個數據源均保存數據正常。下面繼續測試刪除功能,使用兩個數據源的 repository 將用戶信息全部刪除。

@Test
public void testDelete() throws Exception {
    userTest1Repository.deleteAll();
    userTest2Repository.deleteAll();
}

執行完測試用例後,發現 test1 庫和 test2 庫用戶表的信息已經被清空,證明多數據源刪除成功。

總結

Spring Data JPA 通過在啓動時加載不同的數據源,並將不同的數據源注入到不同的 repository 包下,從而實現項目多數據源操作,在項目中使用多數據源時,需要用到哪個數據源,只需要將對應包下的 repository 注入操作即可。本課示例中以兩個數據源作爲演示,但其實三個或者更多數據源配置、操作,都可以按照上面方法進行配置使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章