1 為什么需要多數(shù)據(jù)源
在比較大型的項(xiàng)目中赫模,數(shù)據(jù)庫(kù)可能會(huì)分布在多臺(tái)服務(wù)器上,例如有若干個(gè)數(shù)據(jù)庫(kù)服務(wù)是專門(mén)存放日志數(shù)據(jù)的蒸矛,又有若干個(gè)數(shù)據(jù)庫(kù)服務(wù)是專門(mén)存放業(yè)務(wù)數(shù)據(jù)的等等....這時(shí)候應(yīng)用程序如果需要對(duì)這兩種類型的數(shù)據(jù)進(jìn)行處理的話瀑罗,就需要配置多數(shù)據(jù)源了。
本文將使用的Spring Boot的版本是2.1.1.RELEASE雏掠,其他的版本配置可能會(huì)略有不同斩祭,具體的建議參考Spring Boot官方文檔,文檔中有多數(shù)據(jù)源配置的相關(guān)介紹乡话。并且本文僅針對(duì)JPA的多數(shù)據(jù)源配置摧玫,其他的ORM例如MyBaties不在本文的討論范圍。
2 項(xiàng)目結(jié)構(gòu)
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── top
│ │ │ └── yeonon
│ │ │ └── multidatasource
│ │ │ ├── controller
│ │ │ │ └── HelloController.java
│ │ │ ├── dbconfig
│ │ │ │ ├── DataSourceConfig.java
│ │ │ │ ├── DockerDatasourceConfig.java
│ │ │ │ └── LocalDataSourceConfig.java
│ │ │ ├── entity
│ │ │ │ ├── docker
│ │ │ │ │ └── User.java
│ │ │ │ └── local
│ │ │ │ └── Course.java
│ │ │ ├── MultiDatasourceApplication.java
│ │ │ └── repository
│ │ │ ├── docker
│ │ │ │ └── UserRepository.java
│ │ │ └── local
│ │ │ └── CourseRepository.java
│ │ └── resources
│ │ ├── application.properties
│ │ ├── static
│ │ └── templates
上面是項(xiàng)目的包結(jié)構(gòu)蚊伞,和單一數(shù)據(jù)源的最大區(qū)別就是在entity包和repository包中細(xì)分了不同的數(shù)據(jù)源席赂,例如我這里的是local以及docker,這樣區(qū)分的好處后面會(huì)看到时迫。其他的就沒(méi)什么了颅停,真有需要注意的地方,會(huì)穿插的介紹掠拳。
3 數(shù)據(jù)源的配置
首先是DataSourceConfig類癞揉,該類是一個(gè)配置類(有@Configuration注解的類),我們?cè)谶@里配置數(shù)據(jù)源DataSource:
@Configuration
public class DataSourceConfig {
@Bean(name = "localDatasource")
@Qualifier(value = "localDatasource")
@ConfigurationProperties(prefix = "spring.datasource.local")
@Primary
public DataSource localDatasource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dockerDatasource")
@Qualifier(value = "dockerDatasource")
@ConfigurationProperties(prefix = "spring.datasource.docker")
public DataSource dockerDatasource() {
return DataSourceBuilder.create().build();
}
}
- @Bean注解就表示該方法是用來(lái)配置Bean的,name屬性指示了該Bean的名字喊熟。
- @Qualifier柏肪,因?yàn)槲覀冞@里為DataSource這個(gè)類型配置了兩個(gè)Bean,如果不做特殊處理芥牌,Spring在對(duì)Bean進(jìn)行自動(dòng)注入的時(shí)候烦味,將無(wú)法知道使用哪個(gè)Bean,好的情況是直接報(bào)錯(cuò)壁拉,令人郁悶的情況是Spring不保錯(cuò)谬俄,但是僅僅有一個(gè)Bean生效,即另一個(gè)被覆蓋了弃理,這種情況下的BUG排查是非常麻煩的溃论。
- @ConfigurationProperties,該注解是為該Bean配置一個(gè)與之對(duì)應(yīng)的屬性痘昌,prefix即前綴钥勋,所以在配置local數(shù)據(jù)源的時(shí)候,我們可以這樣配置:spring.datasource.local.dirver-class-name:XXX(其他的同理)辆苔。
- @Primary算灸,用來(lái)標(biāo)識(shí)主庫(kù),這個(gè)注解是必須的姑子,但是也只能有一個(gè)乎婿。各位可以自行嘗試一下不加或者多加會(huì)出現(xiàn)什么錯(cuò)誤,加深理解街佑。
重要的就是這幾個(gè)注解了谢翎,DataSource實(shí)例就直接用DataSourceBuilder來(lái)創(chuàng)建就行了,如果你有自己定制好的DataSource實(shí)例也可以不用DataSourceBuilder來(lái)創(chuàng)建沐旨。
光配置數(shù)據(jù)源的Bean還沒(méi)完森逮,在單數(shù)據(jù)源的情況下,Spring Boot會(huì)通過(guò)自動(dòng)配置將數(shù)據(jù)源DataSource(Spring Boot也會(huì)自動(dòng)的完成對(duì)數(shù)據(jù)源Bean的配置)磁携,然后將其注入到LocalContainerEntityManagerFactoryBean實(shí)體類工廠以及PlatformTransactionManager事務(wù)管理對(duì)象里褒侧,以此完成對(duì)JPA的自動(dòng)配置。但在多數(shù)據(jù)源的情況下谊迄,Spring Boot自動(dòng)配置還不能幫我們完成這樣的注入闷供,所以需要我們手動(dòng)配置LocalContainerEntityManagerFactoryBean以及PlatformTransactionManager。下面來(lái)看代碼:
@Configuration
//開(kāi)啟事務(wù)管理
@EnableTransactionManagement
//開(kāi)啟JpaRepositories
@EnableJpaRepositories(
//在這個(gè)JpaRepositories中用到的事務(wù)管理器统诺,這里是Ref歪脏,即引用
transactionManagerRef = "localTransactionManager",
//實(shí)體類工廠
entityManagerFactoryRef = "localEntityManagerFactory",
//JpaRepositories所在的包名,這也是為什么我們要細(xì)分包名的原因
basePackages = "top.yeonon.multidatasource.repository.local"
)
public class LocalDataSourceConfig {
//這里必須要要加 @Qualifier(value = "localDatasource")注解
//否則Spring將不會(huì)知道用哪個(gè)Bean
@Autowired
@Qualifier(value = "localDatasource")
private DataSource localDatasource;
//JpaProperties粮呢,Spring自動(dòng)配置會(huì)自動(dòng)加載該Bean
@Autowired
private JpaProperties jpaProperties;
//配置實(shí)體類工廠
@Bean(name = "localEntityManagerFactory")
//在數(shù)據(jù)源的配置中婿失,我們將Local配置成了主庫(kù)钞艇,所以必須在這里有@Primary注解
@Primary
public LocalContainerEntityManagerFactoryBean localEntityManagerFactory(EntityManagerFactoryBuilder builder) {
//創(chuàng)建一個(gè)工廠
return builder
.dataSource(localDatasource) //數(shù)據(jù)源實(shí)例,在這里指的就是localDatasource這個(gè)實(shí)例
.properties(jpaProperties.getProperties()) //加Jpa的屬性配置加入進(jìn)來(lái)
.packages("top.yeonon.multidatasource.entity.local") //實(shí)體類包名
.persistenceUnit("localPersistenceUnit")
.build();
}
@Bean(name = "localTransactionManager")
//和上面一樣豪硅,必須要有該注解
@Primary
public PlatformTransactionManager localTransactionManager(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(localEntityManagerFactory(builder).getObject());
}
}
這樣就完成了LocalDatasource的配置哩照,下面直接來(lái)看docker數(shù)據(jù)源的配置吧,和LocalDataSourceConfig非常類似懒浮,就不多說(shuō)了飘弧,關(guān)鍵是注意名稱:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
transactionManagerRef = "dockerTransactionManager",
entityManagerFactoryRef = "dockerEntityManagerFactory",
basePackages = {"top.yeonon.multidatasource.repository.docker"}
)
public class DockerDatasourceConfig {
@Autowired
@Qualifier(value = "dockerDatasource")
private DataSource dockerDatasource;
@Autowired
private JpaProperties jpaProperties;
@Bean(name = "dockerEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean dockerEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dockerDatasource)
.properties(jpaProperties.getProperties())
.packages("top.yeonon.multidatasource.entity.docker")
.persistenceUnit("dockerPersistenceUnit")
.build();
}
@Bean
public PlatformTransactionManager dockerTransactionManager(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(dockerEntityManagerFactory(builder).getObject());
}
}
除了名稱不同之外,還有就是這里不需要也不應(yīng)該加入@Primary注解砚著。配置完了嗎眯牧?其實(shí)還沒(méi)有,別忘了在屬性配置文件中配置url赖草,username等...下面是一個(gè)示例:
spring.datasource.local.jdbc-url=jdbc:mysql://localhost:3306/test?useSSL=false
spring.datasource.local.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.local.username=root
spring.datasource.local.password=124563
spring.datasource.docker.jdbc-url=jdbc:mysql://XXX.XXX.XXX.XXX:4306/test?useSSL=false
spring.datasource.docker.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.docker.username=root
spring.datasource.docker.password=124563
spring.jpa.hibernate.ddl-auto=none
發(fā)現(xiàn)了嗎?這里url配置和以往不太一樣剪个,以前僅僅是url即可秧骑,現(xiàn)在需要使用的屬性名字是jdbc-url,這是新版的Spring Boot修改的扣囊,大家可以嘗試一下沿用原來(lái)的url乎折,看看錯(cuò)誤堆棧,加深理解侵歇。
至此骂澄,就算是完成配置了,下面是一個(gè)簡(jiǎn)單的測(cè)試:
@RestController
@RequestMapping("/hello")
public class HelloController {
@Autowired
private UserRepository userRepository;
@Autowired
private CourseRepository courseRepository;
@GetMapping("user")
public User user(Long id) {
return userRepository.findById(id).orElse(null);
}
@GetMapping("course")
public Course course(Long id) {
return courseRepository.findById(id).orElse(null);
}
}
運(yùn)行后惕虑,往數(shù)據(jù)庫(kù)里加點(diǎn)測(cè)試數(shù)據(jù)坟冲,訪問(wèn)這里配置的路徑,應(yīng)該就能得到期望的結(jié)果了溃蔫。
終于完成了健提,其實(shí)這個(gè)多數(shù)據(jù)源的配置說(shuō)難也不難,但也不簡(jiǎn)單伟叛,關(guān)鍵在于要“細(xì)心”K奖浴!统刮!實(shí)體類工廠的Bean以及事務(wù)管理器的Bean的名字不要寫(xiě)錯(cuò)紊遵,還有就是自動(dòng)注入以及配置DataSource時(shí)候一定要有@Qualifier注解!=拿伞暗膜!而且@Primary也一定要有,不要多配辉哥,也不可少配桦山。
4 小結(jié)
最后攒射,總結(jié)一下整個(gè)配置的流程:
- 先配置數(shù)據(jù)源DataSouce的Bean,因?yàn)橐獮镈ataSouce這個(gè)類型配置多個(gè)Bean恒水,所以要有@Qualifier注解來(lái)標(biāo)識(shí)定位具體是哪個(gè)Bean会放。還要有屬性配置@ConfigurationProperties,否則無(wú)法讀取屬性钉凌,就只能在代碼中配置url咧最,username等屬性了。
- 之后為多個(gè)不同的數(shù)據(jù)源配置transactionManager以及entityManagerFactory御雕,核心是將數(shù)據(jù)源注入到這兩個(gè)玩意兒內(nèi)部矢沿,然后生成與特定數(shù)據(jù)源配套的transactionManager和entityManagerFactory。也還要注意在主庫(kù)的配置中加上@Primary注解酸纲。
- 在屬性配置文件中進(jìn)行屬性配置即可捣鲸。