前兩天,我們已經介紹了關于 JdbcTemplate的多資料源配置 以及 Spring Data JPA的多資料源配置 ,接下來具體說說使用MyBatis時候的多資料源場景該如何配置。
https://blog.didispace.com/spring-boot-learning-21-3-9/#%E6%B7%BB%E5%8A%A0%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90%E7%9A%84%E9%85%8D%E7%BD%AE 添加多資料源的配置
先在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
說明與注意:
- 多資料源配置的時候,與單資料源不同點在于spring.datasource之後多設定一個資料源名稱primary和secondary來區分不同的資料源配置,這個字首将在後續初始化資料源的時候用到。
- 資料源連接配接配置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.,那麼就是這個配置項的問題。
- 可以看到,不論使用哪一種資料通路架構,對于資料源的配置都是一樣的。
https://blog.didispace.com/spring-boot-learning-21-3-9/#%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90%E4%B8%8EMyBatis%E9%85%8D%E7%BD%AE 初始化資料源與MyBatis配置
完成多資料源的配置資訊之後,就來建立個配置類來加載這些配置資訊,初始化資料源,以及初始化每個資料源要用的MyBatis配置。
這裡我們繼續将資料源與架構配置做拆分處理:
- 單獨建一個多資料源的配置類,比如下面這樣:
@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配置上。
- 分别建立兩個資料源的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());
}
}
- 配置類上使用
注解來指定目前資料源下定義的Entity和Mapper的包路徑;另外需要指定sqlSessionFactory和sqlSessionTemplate,這兩個具體實作在該配置類中類中初始化。@MapperScan
- 配置類的構造函數中,通過
注解來指定具體要用哪個資料源,其名字對應在@Qualifier
配置類中的資料源定義的函數名。DataSourceConfiguration
- 配置類中定義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();
}
測試驗證
完成了上面之後,我們就可以寫個測試類來嘗試一下上面的多資料源配置是否正确了,先來設計一下驗證思路:
- 往Primary資料源插入一條資料
- 從Primary資料源查詢剛才插入的資料,配置正确就可以查詢到
- 從Secondary資料源查詢剛才插入的資料,配置正确應該是查詢不到的
- 往Secondary資料源插入一條資料
- 從Primary資料源查詢剛才插入的資料,配置正确應該是查詢不到的
- 從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());
}
}