1. 程式人生 > >Spring Data JPA 多資料來源的使用

Spring Data JPA 多資料來源的使用

1    第3-6課:Spring Data JPA 多資料來源的使用

專案中使用多個數據源在以往工作中比較常見,微服務架構中不建議一個專案使用多個數據源。在微服務架構下,一個微服務擁有自己獨立的一個數據庫,如果此微服務要使用其他資料庫的資料,需要呼叫對應庫的微服務介面來呼叫,而不是在一個專案中連線使用多個數據庫,這樣微服務更獨立、更容易水平擴充套件。

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

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

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

 

其中:

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

下面演示一下專案。

1.1    多資料來源的支援

配置 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" })//設定daorepo)所在位置
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 庫使用者表的資訊已經被清空,證明多資料來源刪除成功。

1.2    總結

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

點選這裡下載原始碼