Spring Boot 2.x基礎教程:MyBatis的多資料來源配置

程式猿DD發表於2020-06-28

前兩天,我們已經介紹了關於JdbcTemplate的多資料來源配置以及Spring Data JPA的多資料來源配置,接下來具體說說使用MyBatis時候的多資料來源場景該如何配置。

新增多資料來源的配置

先在Spring Boot的配置檔案application.properties中設定兩個你要連結的資料庫配置,比如這樣:

spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1
spring.datasource.primary.username=root
spring.datasource.primary.password=123456
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=123456
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

說明與注意

  1. 多資料來源配置的時候,與單資料來源不同點在於spring.datasource之後多設定一個資料來源名稱primary和secondary來區分不同的資料來源配置,這個字首將在後續初始化資料來源的時候用到。
  2. 資料來源連線配置2.x和1.x的配置項是有區別的:2.x使用spring.datasource.secondary.jdbc-url,而1.x版本使用spring.datasource.secondary.url。如果你在配置的時候發生了這個報錯java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.,那麼就是這個配置項的問題。
  3. 可以看到,不論使用哪一種資料訪問框架,對於資料來源的配置都是一樣的。

初始化資料來源與MyBatis配置

完成多資料來源的配置資訊之後,就來建立個配置類來載入這些配置資訊,初始化資料來源,以及初始化每個資料來源要用的MyBatis配置。

這裡我們繼續將資料來源與框架配置做拆分處理:

  1. 單獨建一個多資料來源的配置類,比如下面這樣:
@Configuration
public class DataSourceConfiguration {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

}

可以看到內容跟JdbcTemplate、Spring Data JPA的時候是一模一樣的。通過@ConfigurationProperties可以知道這兩個資料來源分別載入了spring.datasource.primary.*spring.datasource.secondary.*的配置。@Primary註解指定了主資料來源,就是當我們不特別指定哪個資料來源的時候,就會使用這個Bean真正差異部分在下面的JPA配置上。

  1. 分別建立兩個資料來源的MyBatis配置。

Primary資料來源的JPA配置:

@Configuration
@MapperScan(
        basePackages = "com.didispace.chapter39.p",
        sqlSessionFactoryRef = "sqlSessionFactoryPrimary",
        sqlSessionTemplateRef = "sqlSessionTemplatePrimary")
public class PrimaryConfig {

    private DataSource primaryDataSource;

    public PrimaryConfig(@Qualifier("primaryDataSource") DataSource primaryDataSource) {
        this.primaryDataSource = primaryDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryPrimary() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(primaryDataSource);
        return bean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplatePrimary() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactoryPrimary());
    }

}

Secondary資料來源的JPA配置:

@Configuration
@MapperScan(
        basePackages = "com.didispace.chapter39.s",
        sqlSessionFactoryRef = "sqlSessionFactorySecondary",
        sqlSessionTemplateRef = "sqlSessionTemplateSecondary")
public class SecondaryConfig {

    private DataSource secondaryDataSource;

    public SecondaryConfig(@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
        this.secondaryDataSource = secondaryDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactorySecondary() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(secondaryDataSource);
        return bean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplateSecondary() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactorySecondary());
    }

}

說明與注意

  1. 配置類上使用@MapperScan註解來指定當前資料來源下定義的Entity和Mapper的包路徑;另外需要指定sqlSessionFactory和sqlSessionTemplate,這兩個具體實現在該配置類中類中初始化。
  2. 配置類的建構函式中,通過@Qualifier註解來指定具體要用哪個資料來源,其名字對應在DataSourceConfiguration配置類中的資料來源定義的函式名。
  3. 配置類中定義SqlSessionFactory和SqlSessionTemplate的實現,注意具體使用的資料來源正確(如果使用這裡的演示程式碼,只要第二步沒問題就不需要修改)。

上一篇介紹JPA的時候,因為之前介紹JPA的使用時候,說過實體和Repository定義的方法,所以省略了 User 和 Repository的定義程式碼,但是還是有讀者問怎麼沒有這個,其實都有說明,倉庫程式碼裡也都是有的。未避免再問這樣的問題,所以這裡就貼一下吧。

根據上面Primary資料來源的定義,在com.didispace.chapter39.p包下,定義Primary資料來源要用的實體和資料訪問物件,比如下面這樣:

@Data
@NoArgsConstructor
public class UserPrimary {

    private Long id;

    private String name;
    private Integer age;

    public UserPrimary(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

public interface UserMapperPrimary {

    @Select("SELECT * FROM USER WHERE NAME = #{name}")
    UserPrimary findByName(@Param("name") String name);

    @Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})")
    int insert(@Param("name") String name, @Param("age") Integer age);

    @Delete("DELETE FROM USER")
    int deleteAll();

}

根據上面Secondary資料來源的定義,在com.didispace.chapter39.s包下,定義Secondary資料來源要用的實體和資料訪問物件,比如下面這樣:

@Data
@NoArgsConstructor
public class UserSecondary {

    private Long id;

    private String name;
    private Integer age;

    public UserSecondary(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

public interface UserMapperSecondary {

    @Select("SELECT * FROM USER WHERE NAME = #{name}")
    UserSecondary findByName(@Param("name") String name);

    @Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})")
    int insert(@Param("name") String name, @Param("age") Integer age);

    @Delete("DELETE FROM USER")
    int deleteAll();
}

測試驗證

完成了上面之後,我們就可以寫個測試類來嘗試一下上面的多資料來源配置是否正確了,先來設計一下驗證思路:

  1. 往Primary資料來源插入一條資料
  2. 從Primary資料來源查詢剛才插入的資料,配置正確就可以查詢到
  3. 從Secondary資料來源查詢剛才插入的資料,配置正確應該是查詢不到的
  4. 往Secondary資料來源插入一條資料
  5. 從Primary資料來源查詢剛才插入的資料,配置正確應該是查詢不到的
  6. 從Secondary資料來源查詢剛才插入的資料,配置正確就可以查詢到

具體實現如下:

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class Chapter39ApplicationTests {

    @Autowired
    private UserMapperPrimary userMapperPrimary;
    @Autowired
    private UserMapperSecondary userMapperSecondary;

    @Before
    public void setUp() {
        // 清空測試表,保證每次結果一樣
        userMapperPrimary.deleteAll();
        userMapperSecondary.deleteAll();
    }

    @Test
    public void test() throws Exception {
        // 往Primary資料來源插入一條資料
        userMapperPrimary.insert("AAA", 20);

        // 從Primary資料來源查詢剛才插入的資料,配置正確就可以查詢到
        UserPrimary userPrimary = userMapperPrimary.findByName("AAA");
        Assert.assertEquals(20, userPrimary.getAge().intValue());

        // 從Secondary資料來源查詢剛才插入的資料,配置正確應該是查詢不到的
        UserSecondary userSecondary = userMapperSecondary.findByName("AAA");
        Assert.assertNull(userSecondary);

        // 往Secondary資料來源插入一條資料
        userMapperSecondary.insert("BBB", 20);

        // 從Primary資料來源查詢剛才插入的資料,配置正確應該是查詢不到的
        userPrimary = userMapperPrimary.findByName("BBB");
        Assert.assertNull(userPrimary);

        // 從Secondary資料來源查詢剛才插入的資料,配置正確就可以查詢到
        userSecondary = userMapperSecondary.findByName("BBB");
        Assert.assertEquals(20, userSecondary.getAge().intValue());
    }

}

程式碼示例

本文的相關例子可以檢視下面倉庫中的chapter3-9目錄:

如果您覺得本文不錯,歡迎Star支援,您的關注是我堅持的動力!

相關文章