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
publicclass 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(),
newHibernateSettings
());
}
第一個資料來源的載入配置過程
首先來看第一個資料來源的載入配置過程,建立 PrimaryConfig 類,將上面建立好的第一個資料來源注入到類中,新增 @Configuration 和 @EnableTransactionManagement 註解,第一個代表啟動時載入,第二個註解表示啟用事務,同時將第一個資料來源和 JPA 配置資訊注入到類中。
@Configuration
@EnableTransactionManagement
publicclass 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)所在位置
publicclass 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"})
publicclass 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
publicclass UserRepositoryTests
{
@Resource
private
UserTest1Repository userTest1Repository;
@Resource
private
UserTest2Repository userTest2Repository;
}
首先測試兩個資料庫中都存入資料,資料來源1插入 2 條使用者資訊,資料來源2插入 1 條使用者資訊。
@Test
public void testSave() throws Exception {
Date date =
newDate();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
String formattedDate = dateFormat.format(date);
userTest1Repository.save(
newUser(
"aa",
"aa123456",
"[email protected]",
"aa", formattedDate));
userTest1Repository.save(
newUser(
"bb",
"bb123456",
"[email protected]",
"bb", formattedDate));
userTest2Repository.save(
newUser(
"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 注入操作即可。本課示例中以兩個資料來源作為演示,但其實三個或者更多資料來源配置、操作,都可以按照上面方法進行配置使用。