1. 程式人生 > >MyBatis初級實戰之四:druid多資料來源

MyBatis初級實戰之四:druid多資料來源

### 歡迎訪問我的GitHub [https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) 內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等; ### 關於druid多資料來源 本文是《MyBatis初級實戰》系列的第四篇,一個springboot應用同時操作兩個資料庫的場景,在平時也會遇到,今天要實戰的就是通過druid配置兩個資料來源,讓一個springboot應用同時使用這兩個資料來源; ### 多資料來源配置的基本思路 1. 首先要明確的是:資料來源是通過配置類實現的,因此要去掉springboot中和資料來源相關的自動裝配; 2. 最核心的問題有兩個,第一個是確定表和資料來源的關係,這個關係是在SqlSessionFactory例項中確立的,程式碼如下所示: ```java @Bean(name = "secondSqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/second/**/*Mapper.xml")); return bean.getObject(); } ``` 3. 第二個核心問題是包掃描,即指定的mapper介面要使用指定的sqlSessionTemplat,這個關係在SqlSessionTemplate配置類中(相當於舊版的xml配置bean),如下圖所示: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202101/485422-20210120085441107-1259508074.png) 4. 從上述程式碼可見,如果上層的業務程式碼想操作secondDataSource這個資料來源的表,只要把對應的*Mapper.xml檔案和Mapper介面檔案對應的目錄下即可; 5. 整個配置的關鍵步驟如下圖所示: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202101/485422-20210120085441496-416949163.png) ### 實戰概覽 本次實戰的內容如下: 1. 一共有兩個資料庫:mybatis和mybatis_second; 2. mybatis中有名為user的表,mybatis_second中有名為address的表; 3. 新建名為druidtwosource的springboot應用,裡面有兩個controller,可以分別對user、address這兩個表進行操作; 4. 編寫單元測試用例,通過呼叫controller介面驗證應用功能正常; 5. 啟動springboot應用,通過swagger驗證功能正常; 6. 進入druid監控頁面; ### 原始碼下載 1. 如果您不想編碼,可以在GitHub下載所有原始碼,地址和連結資訊如下表所示(https://github.com/zq2599/blog_demos): | 名稱 | 連結 | 備註| | :-------- | :----| :----| | 專案主頁| https://github.com/zq2599/blog_demos | 該專案在GitHub上的主頁 | | git倉庫地址(https)| https://github.com/zq2599/blog_demos.git | 該專案原始碼的倉庫地址,https協議 | | git倉庫地址(ssh)| [email protected]:zq2599/blog_demos.git | 該專案原始碼的倉庫地址,ssh協議 | 2. 這個git專案中有多個資料夾,本章的應用在mybatis資料夾下,如下圖紅框所示: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202101/485422-20210120085441666-11943590.png) ### 建立資料庫和表 1. 建立名為mybatis的資料庫,建表語句如下: ```sql DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(32) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `age` int(32) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; ``` 2. 建立名為mybatis_second的資料庫,建表語句如下: ```sql DROP TABLE IF EXISTS `address`; CREATE TABLE `address` ( `id` int(32) NOT NULL AUTO_INCREMENT, `city` varchar(32) NOT NULL, `street` varchar(32) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; ``` ### 編碼 1. 前文[《MyBatis初級實戰之一:Spring Boot整合》](https://blog.csdn.net/boling_cavalry/article/details/107805840)建立了父工程mybatis,本文繼續在此工程中新增子工程,名為druidtwosource,先提前看整個子工程檔案結構,如下圖,要注意的是紅框1中的mapper介面,以及紅框2中的mapper對映檔案,這兩處都按照資料庫的不同放入各自資料夾: ![在這裡插入圖片描述](https://img2020.cnblogs.com/other/485422/202101/485422-20210120085441959-1234750326.png) 2. druidtwosource工程的pom.xml內容如下: ```xml ``` 3. 配置檔案application.yml,可見這裡面有first和second兩個資料來源配置,而druid的web-stat-filter和stat-view-servlet這兩個配置是公用的: ```yml server: port: 8080 spring: #1.JDBC資料來源 datasource: druid: first: username: root password: 123456 url: jdbc:mysql://192.168.50.43:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver #初始化連線池的連線數量 大小,最小,最大 initial-size: 5 min-idle: 5 max-active: 20 #配置獲取連線等待超時的時間 max-wait: 60000 #配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 time-between-eviction-runs-millis: 60000 # 配置一個連線在池中最小生存的時間,單位是毫秒 min-evictable-idle-time-millis: 30000 # 配置一個連線在池中最大生存的時間,單位是毫秒 max-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM user test-while-idle: true test-on-borrow: true test-on-return: false # 是否快取preparedStatement,也就是PSCache 官方建議MySQL下建議關閉 個人建議如果想用SQL防火牆 建議開啟 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 # 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆 filters: stat,wall,slf4j filter: stat: merge-sql: true slow-sql-millis: 5000 second: username: root password: 123456 url: jdbc:mysql://192.168.50.43:3306/mybatis_second?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver #初始化連線池的連線數量 大小,最小,最大 initial-size: 5 min-idle: 5 max-active: 20 #配置獲取連線等待超時的時間 max-wait: 60000 #配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 time-between-eviction-runs-millis: 60000 # 配置一個連線在池中最小生存的時間,單位是毫秒 min-evictable-idle-time-millis: 30000 # 配置一個連線在池中最大生存的時間,單位是毫秒 max-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM user test-while-idle: true test-on-borrow: true test-on-return: false # 是否快取preparedStatement,也就是PSCache 官方建議MySQL下建議關閉 個人建議如果想用SQL防火牆 建議開啟 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 # 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆 filters: stat,wall,slf4j filter: stat: merge-sql: true### slow-sql-millis: 5000 #3.基礎監控配置 web-stat-filter: enabled: true url-pattern: /* #設定不統計哪些URL exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" session-stat-enable: true session-stat-max-count: 100 stat-view-servlet: enabled: true url-pattern: /druid/* reset-enable: true #設定監控頁面的登入名和密碼 login-username: admin login-password: admin allow: 127.0.0.1 #deny: 192.168.1.100 # 日誌配置 logging: level: root: INFO com: bolingcavalry: druidtwosource: mapper: debug ``` 4. user的對映配置,請注意檔案位置: ```xml ``` 5. address的對映配置: ```xml ``` 6. user表的實體類,注意swagger用到的註解: ```java package com.bolingcavalry.druidtwosource.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @ApiModel(description = "使用者實體類") public class User { @ApiModelProperty(value = "使用者ID") private Integer id; @ApiModelProperty(value = "使用者名稱", required = true) private String name; @ApiModelProperty(value = "使用者地址", required = false) private Integer age; @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } ...省略get和set方法 } ``` 7. address表的實體類: ```java package com.bolingcavalry.druidtwosource.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @ApiModel(description = "地址實體類") public class Address { @ApiModelProperty(value = "地址ID") private Integer id; @ApiModelProperty(value = "城市名", required = true) private String city; @ApiModelProperty(value = "街道名", required = true) private String street; @Override public String toString() { return "Address{" + "id=" + id + ", city='" + city + '\'' + ", street='" + street + '\'' + '}'; } ...省略get和set方法 } ``` 8. 啟動類DuridTwoSourceApplication.java,要注意的是排除掉資料來源和事務的自動裝配,因為後面會手動編碼執行這些配置: ```java package com.bolingcavalry.druidtwosource; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; @SpringBootApplication(exclude={ DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, }) public class DuridTwoSourceApplication { public static void main(String[] args) { SpringApplication.run(DuridTwoSourceApplication.class, args); } } ``` 9. swagger配置: ```java package com.bolingcavalry.druidtwosource; import springfox.documentation.service.Contact; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Tag; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * @Description: swagger配置類 * @author: willzhao E-mail: [email protected] * @date: 2020/8/11 7:54 */ @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .tags(new Tag("UserController", "使用者服務"), new Tag("AddressController", "地址服務")) .select() // 當前包路徑 .apis(RequestHandlerSelectors.basePackage("com.bolingcavalry.druidtwosource.controller")) .paths(PathSelectors.any()) .build(); } //構建 api文件的詳細資訊函式,注意這裡的註解引用的是哪個 private ApiInfo apiInfo() { return new ApiInfoBuilder() //頁面標題 .title("MyBatis CURD操作") //建立人 .contact(new Contact("程式設計師欣宸", "https://github.com/zq2599/blog_demos", "[email protected]")) //版本號 .version("1.0") //描述 .description("API 描述") .build(); } } ``` 10. 資料來源配置TwoDataSourceConfig.java,可見是通過ConfigurationProperties註解來確定配置資訊,另外不要忘記在預設資料來源上新增Primary註解: ```java package com.bolingcavalry.druidtwosource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; /** * @Description: druid配置類 * @author: willzhao E-mail: [email protected] * @date: 2020/8/18 08:12 */ @Configuration public class TwoDataSourceConfig { @Primary @Bean(name = "firstDataSource") @ConfigurationProperties("spring.datasource.druid.first") public DataSource first() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "secondDataSource") @ConfigurationProperties("spring.datasource.druid.second") public DataSource second() { return DruidDataSourceBuilder.create().build(); } } ``` 11. 第一個資料來源的mybatis配置類DruidConfigFirst.java,可以結合本篇的第一幅圖來看,注意MapperScan註解的兩個屬性basePackages和sqlSessionTemplateRef是關鍵,它們最終決定了哪些mapper介面使用哪個資料來源,另外注意要帶上Primary註解: ```java package com.bolingcavalry.druidtwosource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; /** * @Description: druid配置類 * @author: willzhao E-mail: [email protected] * @date: 2020/8/18 08:12 */ @Configuration @MapperScan(basePackages = "com.bolingcavalry.druidtwosource.mapper.first", sqlSessionTemplateRef = "firstSqlSessionTemplate") public class DruidConfigFirst { @Bean(name = "firstSqlSessionFactory") @Primary public SqlSessionFactory sqlSessionFactory(@Qualifier("firstDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/first/**/*Mapper.xml")); return bean.getObject(); } @Bean(name = "firstTransactionManager") @Primary public DataSourceTransactionManager transactionManager(@Qualifier("firstDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "firstSqlSessionTemplate") @Primary public SqlSessionTemplate sqlSessionTemplate(@Qualifier("firstSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } ``` 12. 第二個資料來源的mybatis配置DruidConfigSecond.java,注意不要帶Primary註解: ```java package com.bolingcavalry.druidtwosource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; /** * @Description: druid配置類 * @author: willzhao E-mail: [email protected] * @date: 2020/8/18 08:12 */ @Configuration @MapperScan(basePackages = "com.bolingcavalry.druidtwosource.mapper.second", sqlSessionTemplateRef = "secondSqlSessionTemplate") public class DruidConfigSecond { @Bean(name = "secondSqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/second/**/*Mapper.xml")); return bean.getObject(); } @Bean(name = "secondTransactionManager") public DataSourceTransactionManager transactionManager(@Qualifier("secondDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "secondSqlSessionTemplate") public SqlSessionTemplate sqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } ``` 13. user表的mapper介面類很簡單,只有三個介面,注意package位置: ```java package com.bolingcavalry.druidtwosource.mapper.first; import com.bolingcavalry.druidtwosource.entity.User; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface UserMapper { int insertWithFields(User user);