1. 程式人生 > >SpringBoot多資料來源及MyBatis配置詳解

SpringBoot多資料來源及MyBatis配置詳解

前言

最近迫於專案需要,筆者踏上了springboot多資料來源的配置之旅。之前筆者配置過spring的動態多資料來源切換,當時使用的是JDBC Template。

目前專案中持久化框架使用是mybatis,經過分析後不難發現,多資料來源配置需要解決兩個問題,一個是由原先的spring經典方式切換到了springboot方式下,多資料來源如何配置?有無太大變化?另一個是怎樣將多資料來源與mybatis的配置關聯起來?

不妨先來看下,單資料來源下mybatis如何配置的?

單資料來源示例

首先要宣告一點,專案只是依賴單個數據源時,如果你不介意springboot幫你做事的話,那麼恭喜你,你省事兒了!你只需要在專案的屬性檔案中新增資料來源的相關屬性配置,springboot會“免費”提供給你一個數據源使用,預設採用的是

tomcat jdbc connection pool

當然你可以拒絕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配置的一般步驟:

  1. 配置資料來源DataSource的Bean。
  2. 使用DataSource配置事務管理器。
  3. 使用DataSource配置SqlSessionFactory的Bean。
  4. 配置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);
    }
 }

不難看出兩個資料來源的配置步驟吧:

  1. 在屬性檔案中配置兩個資料來源需要用到的屬性值,注意起個好點的字首名稱。
  2. 構建兩個資料來源的配置類,當然這不是必須的,願意堆在一個配置類中未嘗不可。
  3. 配置類中,配置DataSource的Bean,記得起個能夠標識的name!
  4. 配置兩個資料來源各自對應的事務管理器,別嫌麻煩,否則會給自己埋坑裡,記得起個能夠標識的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();
    }
 }

這裡需要強調幾個地方:

  1. 細心人會發現上面的配置類中,少了MapperScannerConfigurer的Bean配置,改用了@MapperScan註解。其實兩者的作用是一樣的,但是@MapperScan比較新,稍後會做解釋為什麼它比較新。
  2. 由於兩個資料來源的原因,引出了兩套SqlSessionFactory的配置,所以@MapperScan中需要指明依賴的是哪個SqlSessionFactory,“sqlSessionFactoryRef”對應就是SqlSessionFactory的name屬性。
  3. @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會幫你做關聯。