SpringBoot多資料來源及MyBatis配置詳解
前言
最近迫於專案需要,筆者踏上了springboot多資料來源的配置之旅。之前筆者配置過spring的動態多資料來源切換,當時使用的是JDBC Template。
目前專案中持久化框架使用是mybatis,經過分析後不難發現,多資料來源配置需要解決兩個問題,一個是由原先的spring經典方式切換到了springboot方式下,多資料來源如何配置?有無太大變化?另一個是怎樣將多資料來源與mybatis的配置關聯起來?
不妨先來看下,單資料來源下mybatis如何配置的?
單資料來源示例
首先要宣告一點,專案只是依賴單個數據源時,如果你不介意springboot幫你做事的話,那麼恭喜你,你省事兒了!你只需要在專案的屬性檔案中新增資料來源的相關屬性配置,springboot會“免費”提供給你一個數據源使用,預設採用的是
當然你可以拒絕springboot的好意,如果你依賴第三方的連線池技術,你可以配置自己的資料來源,那麼springboot檢測到你自己定義了DataSource後,就不會自動配置資料來源了。
筆者不能拒絕springboot的好意,所以僅在專案的application.properties中添加了如下屬性:
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class -name=com.mysql.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validation-query=SELECT 1
spring.datasource.test-on-borrow=false
spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=18800
然後筆者建立了一個專門用於配置mybatis的類,如下:
@Configuration
public class MybatisSpringConfig {
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("demo.model");
return factoryBean.getObject();
}
[[[@Bean](http://my.oschina.net/bean)](http://my.oschina.net/bean)](http://my.oschina.net/bean)
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("demo.repository");
return mapperScannerConfigurer;
}
}
沒錯,mybatis在spring中就是可以通過如此的簡練配置進而正常工作起來。你無需刻意地去建立mybatis的配置檔案,無需刻意地去註冊mapper介面及指定對應xml檔案的位置,這完全得益於mybatis-spring,它就像一個“粘合劑”,可以很方便地將mybatis和spring“粘合”在一起。
MyBatis-Spring的配置步驟
不妨先來說下mybatis-spring配置的一般步驟:
- 配置資料來源DataSource的Bean。
- 使用DataSource配置事務管理器。
- 使用DataSource配置SqlSessionFactory的Bean。
- 配置MapperScannerConfigurer的Bean。
這裡要求配置事務管理器和SqlSessionFactory的資料來源必須是同一個,否則事務管理不起作用。配置MapperScannerConfigurer的目的是自動掃描mapper介面所在的包,自動幫你將mapper介面註冊為Bean(代理生成介面的實現類),你就可以直接拿來依賴注入了,建議將mapper介面及其對應的xml檔案放在同一個包下,這樣的話你無需在SqlSessionFactory裡指定xml檔案的位置了。
OK,到此對比我上面貼出的配置類內容,你可能會發現筆者怎麼少了幾步?感謝springboot,因為它自動配置了一個DataSource,同時它還自動配置了一個事務管理器。所以筆者只配置了SqlSessionFactory和MapperScannerConfigurer。
當然如果看到這裡你仍然“執意”要配置自己的資料來源,參照下面的多資料來源配置說明,抽出來多箇中的一個就可以實現自定義單資料來源的配置了。
多資料來源示例
經過上面單資料來源的示例,可以說當我們切換到springboot的方式下寫程式碼時,springboot為我們帶來了很大的便利,還不影響我們自定義,所以筆者認為,沒用使用springboot之前,無論你使用spring怎樣的配置,使用springboot之後,不會有阻礙,甚至會比原來更快!
簡單說下需要多資料來源的場景,筆者參照了一下其他的文章,絕大部分的需要來自於資料庫主從方式或讀寫分離。那麼就按照master和slave兩個資料來源,直接貼出資料來源的配置類。
- application.properties
datasource.master.url=jdbc:mysql://localhost:3306/master
datasource.master.username=root
datasource.master.password=root
datasource.master.driver-class-name=com.mysql.jdbc.Driver
datasource.master.max-idle=10
datasource.master.max-wait=10000
datasource.master.min-idle=5
datasource.master.initial-size=5
datasource.master.validation-query=SELECT 1
datasource.master.test-on-borrow=false
datasource.master.test-while-idle=true
datasource.master.time-between-eviction-runs-millis=18800
datasource.slave.url=jdbc:mysql://localhost:3306/slave
datasource.slave.username=root
datasource.slave.password=root
datasource.slave.driver-class-name=com.mysql.jdbc.Driver
datasource.slave.max-idle=10
datasource.slave.max-wait=10000
datasource.slave.min-idle=5
datasource.slave.initial-size=5
datasource.slave.validation-query=SELECT 1
datasource.slave.test-on-borrow=false
datasource.slave.test-while-idle=true
datasource.slave.time-between-eviction-runs-millis=18800
- master資料來源
@Configuration
public class MasterConfig {
[[[@Primary](http://my.oschina.net/primary)](http://my.oschina.net/primary)](http://my.oschina.net/primary)
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "datasource.master")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "masterTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
- slave資料來源
@Configuration
public class SlaveConfig {
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "datasource.slave")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
不難看出兩個資料來源的配置步驟吧:
- 在屬性檔案中配置兩個資料來源需要用到的屬性值,注意起個好點的字首名稱。
- 構建兩個資料來源的配置類,當然這不是必須的,願意堆在一個配置類中未嘗不可。
- 配置類中,配置DataSource的Bean,記得起個能夠標識的name!
- 配置兩個資料來源各自對應的事務管理器,別嫌麻煩,否則會給自己埋坑裡,記得起個能夠標識的name!
配置DataSource時,利用@ConfigurationProperties(prefix = "xxx.xxx")可以依靠指定的字首,在諸多的屬性值中“挑選”出資料來源依賴的屬性,進而完成資料來源的構建。
當自己定義了DataSource後,springboot就會取消自動配置的動作了。為了各司其職,為每個資料來源配置各自的事務管理器,springboot自然也會取消自動配置事務管理器的動作。由於是多個數據源和多個事務管理器,都是一個型別的,你要是不起個區別的名字,任誰都分辨不出來吧?
@Primary 有什麼作用呢?簡單地說,當有兩個同一型別的Bean,依賴注入時你沒有指定name,正常情況下會報錯,有兩個你要的Bean,識別不了。但是 @Primary 相當於指定這個Bean為預設的,如果你沒有指定name,就採用 @Primary 標識的Bean。
OK,兩個資料來源的配置配好了,還需要配置各自的Mybatis來進行持久化的操作。
MyBatis-Spring相關配置
- mybatis for master
@Configuration
@MapperScan(basePackages = {"demo.repository.master"}, sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterConfig {
@Primary
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "datasource.master")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "masterTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Primary
@Bean(name = "masterSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("demo.model");
return factoryBean.getObject();
}
}
- mybatis for slave
@Configuration
@MapperScan(basePackages = {"demo.repository.slave"}, sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveConfig {
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "datasource.slave")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "slaveSqlSessionFactory")
public SqlSessionFactory basicSqlSessionFactory(@Qualifier("slaveDataSource") DataSource basicDataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(basicDataSource);
factoryBean.setTypeAliasesPackage("demo.model");
return factoryBean.getObject();
}
}
這裡需要強調幾個地方:
- 細心人會發現上面的配置類中,少了MapperScannerConfigurer的Bean配置,改用了@MapperScan註解。其實兩者的作用是一樣的,但是@MapperScan比較新,稍後會做解釋為什麼它比較新。
- 由於兩個資料來源的原因,引出了兩套SqlSessionFactory的配置,所以@MapperScan中需要指明依賴的是哪個SqlSessionFactory,“sqlSessionFactoryRef”對應就是SqlSessionFactory的name屬性。
- @MapperScan會將掃描的mapper介面代理生成實現類,並自動註冊為Bean。由於兩個資料來源的配置類中都有@MapperScan註解,為了避免造成衝突和排錯時的困擾,猛烈提醒,兩個資料來源的配置,mybatis對應的mapper介面及對應xml檔案也構建兩套,最好介面名上也做些區分。model類使用同一套倒是沒什麼影響。所以你會看到上面的配置中,@MapperScan中basePackages指向的是兩個包路徑。
好了,來解釋下@MapperScan為何比較新,並且筆者推薦使用@MapperScan。
首先@MapperScan要求的mybatis-spring版本比較新,說明它是新推出的特性。
其次@MapperScan要比配置MapperScannerConfigurer的Bean要簡練的多,程式碼量上就看得出來。
最後,@MapperScan中的basePackageClasses屬性是MapperScannerConfigurer所沒有的。並且筆者用到了這個basePackageClasses屬性,所以這裡強力推薦使用@MapperScan註解。
多聊一些,描述下筆者為何會用到@MapperScan中的basePackageClasses屬性吧,況且與上述示例中的basePackages有何區別呢?
上面提到了多資料來源的一般場景,筆者的不同。筆者的專案中劃分了n個子模組,每個子模組有各自的資料庫,現在需要每個子模組共享一個公共資訊的資料庫。
從程式碼上來說,由於各個子模組依賴的公共資訊資料庫-資料來源、mapper介面和xml對映檔案是相同的,筆者希望將這些類和檔案抽離到maven的一個公共module(最後會打包為一個jar檔案)中,供其他n個子模組依賴使用,這樣可以避免重複程式碼嘛。
筆者這麼做之後,發現配置MapperScannerConfigurer的basePackages找不到mapper介面所在的包路徑,因為筆者是在子模組中配置的MapperScannerConfigurer,它自然會在子模組的結構中去尋找指定的包路徑,是mapper介面被筆者放到了公共的module中,所以是找不到的!
不過還好在@MapperScan中發現了basePackageClasses屬性,它會“接受”你指定的mapper介面的全名。再次提醒,記得把xml對映檔案和mapper介面放在一起,mybatis-spring會幫你做關聯。