項目中使用多個數據源在以往工作中比較常見,微服務架構中不建議一個項目使用多個數據源。在微服務架構下,一個微服務擁有自己獨立的一個數據庫,如果此微服務要使用其他數據庫的數據,需要調用對應庫的微服務接口來調用,而不是在一個項目中連接使用多個數據庫,這樣微服務更獨立、更容易水平擴展。
雖然在微服務架構下,不提倡一個項目擁有多個數據源,但在 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 注入操作即可。本課示例中以兩個數據源作爲演示,但其實三個或者更多數據源配置、操作,都可以按照上面方法進行配置使用。