Spring Boot從入門到精通(五)多資料來源配置實現及原始碼分析
多資料來源配置在專案軟體中是比較常見的開發需求,Spring和Spring Boot中對此都有相應的解決方案可供大家參考。在Spring Boot中,如MyBatis、JdbcTemplate以及Jpa都可以配置多資料來源。
本文在前一篇“Spring Boot從入門到精通(四)連線MySQL資料庫(附原始碼)”文章中專案原始碼的基礎上,來實現Spring Boot整合MyBatis和使用JdbcTemplate兩種方式配置多資料來源。
Spring Boot整合MyBatis和使用JdbcTemplate配置公共檔案
1、配置資料來源application.properties檔案
假定有兩個資料來源來配置實現,分別對應的名字是oneDataSource和twoDataSource。
在application.properties檔案中配置資料來源資訊如下:
spring.datasource.one.url=jdbc:mysql://123.57.47.154:3306/springboot1 spring.datasource.one.username=root spring.datasource.one.password=wangyoodb spring.datasource.one.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.two.url=jdbc:mysql://123.57.47.154:3306/springboot2 spring.datasource.two.username=root spring.datasource.two.password=wangyoodb spring.datasource.two.driverClassName=com.mysql.cj.jdbc.Driver
2、資料來源配置java類檔案
通過上一步操作,利用關鍵詞one和two對資料來源進行區分,只是簡單增加這些資訊是無法被Spring Boot自動載入的,需要增加程式碼來實現載入DataSource,下面小編手動配置DataSourceConfig,用來提供DataSource Bean。
資料來源一:新增命名DataSourceOneConfig的java類檔案,具體程式碼如下:
package com.yoodb.study.demo03.datasource; 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.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; 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 javax.sql.DataSource; @Configuration @MapperScan(basePackages = "com.yoodb.study.demo03.mapper.one", sqlSessionFactoryRef = "oneSqlSessionFactory") public class DataSourceOneConfig { @Bean(name = "oneDataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource.one") public DataSource getDateSourceOne() { return DataSourceBuilder.create().build(); } @Bean(name = "oneSqlSessionFactory") @Primary public SqlSessionFactory oneSqlSessionFactory(@Qualifier("oneDataSource") DataSource datasource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(datasource); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/one/*.xml")); return bean.getObject(); } // @Bean("oneSqlSessionTemplate") @Primary public SqlSessionTemplate onesqlsessiontemplate( @Qualifier("oneSqlSessionFactory") SqlSessionFactory sessionfactory) { return new SqlSessionTemplate(sessionfactory); } }
資料來源二:新增命名DataSourceTwoConfig的java類檔案,具體程式碼如下:
package com.yoodb.study.demo03.datasource; import com.zaxxer.hikari.HikariDataSource; 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.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource; @Configuration @MapperScan(basePackages = "com.yoodb.study.demo03.mapper.two", sqlSessionFactoryRef = "twoSqlSessionFactory") public class DataSourceTwoConfig { @Bean(name = "twoDataSource") @ConfigurationProperties(prefix = "spring.datasource.two") public DataSource getDateSourceTwo(DataSourceProperties properties) { return DataSourceBuilder.create(properties.getClassLoader()) .type(HikariDataSource.class) .driverClassName(properties.determineDriverClassName()) .url(properties.determineUrl()) .username(properties.determineUsername()) .password(properties.determinePassword()) .build(); } @Bean(name = "twoSqlSessionFactory") public SqlSessionFactory twoSqlSessionFactory(@Qualifier("twoDataSource") DataSource datasource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(datasource); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/two/*.xml")); return bean.getObject(); } @Bean("twoSqlSessionTemplate") public SqlSessionTemplate twosqlsessiontemplate( @Qualifier("twoSqlSessionFactory") SqlSessionFactory sessionfactory) { return new SqlSessionTemplate(sessionfactory); } }
在上述程式碼中使用的註解含義可以參考之前寫過“Spring Boot從入門到精通(三)常用註解含義及用法分析總結”的文章(見微信公眾號“Java精選”),具體使用註解含義如下:
@Configuration
用於定義配置類,可替換xml配置檔案,被註解的類內部包含有一個或多個被@Bean註解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。
@MapperScan
指定要掃描mapper類包的路徑,配置mybatis的介面存放位置。
@Bean
用於告訴方法產生一個Bean物件,然後這個Bean物件交給Spring管理。
@ConfigurationProperties
是Spring Boot提供的型別安全的屬性繫結,以第一個Bean為例,@ConfigurationProperties(prefix = “spring.datasource.one”)表示使用spring.datasource.one字首的資料庫配置去建立一個DataSource。
@Primary
可以理解為預設優先選擇,不可以同時設定多個,必須增加此註解用於區分主資料庫(預設資料庫)。
@Qualifier
來達到注入某個特指bean的作用,表示查詢Spring容器中指定名字的物件。
注意:
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(“***”)) mapper的xml形式檔案位置必須要配置,不然專案會報no statement錯誤資訊。
3、實體類檔案
新增實體類檔案,兩個資料來源公用一個實體類,具體程式碼如下:
package com.yoodb.study.demo03.bean; public class BootUser { private String id; private String name; private String detail; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } }
整合Mybatis
1、新增mapper xml檔案
資料來源一:在src/main/resources/mapper/one(不存在檔案加新建)建立BootUserOneMapper.xml檔案,具體配置資訊如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.yoodb.study.demo03.mapper.one.BootUserOneMapper" > <resultMap id="BaseResultMap" type="com.yoodb.study.demo03.bean.BootUser" > <id column="id" property="id" jdbcType="VARCHAR" /> <result column="user_name" property="name" jdbcType="VARCHAR" /> <result column="detail" property="detail" jdbcType="VARCHAR" /> </resultMap> <select id="selectAll" resultMap="BaseResultMap"> select id, user_name, detail from boot_user order by detail asc </select> </mapper>
資料來源二:在src/main/resources/mapper/two(不存在檔案加新建)建立BootUserTwoMapper.xml檔案,具體配置資訊如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.yoodb.study.demo03.mapper.two.BootUserTwoMapper" > <resultMap id="BaseResultMap" type="com.yoodb.study.demo03.bean.BootUser" > <id column="id" property="id" jdbcType="VARCHAR" /> <result column="user_name" property="name" jdbcType="VARCHAR" /> <result column="detail" property="detail" jdbcType="VARCHAR" /> </resultMap> <select id="selectAll" resultMap="BaseResultMap"> select id, user_name, detail from boot_user order by detail asc </select> </mapper>
2、新增mapper介面類檔案
資料來源一:mapper介面類檔案,具體程式碼如下:
package com.yoodb.study.demo03.mapper.one; import com.yoodb.study.demo03.bean.BootUser; import java.util.List; public interface BootUserOneMapper { List<BootUser> selectAll(); }
資料來源二:mapper介面類檔案,具體程式碼如下:
package com.yoodb.study.demo03.mapper.two; import com.yoodb.study.demo03.bean.BootUser; import java.util.List; public interface BootUserTwoMapper { List<BootUser> selectAll(); }
3、建立service類檔案
新增檔名BootUserService類檔案,具體程式碼如下:
package com.yoodb.study.demo03.service; import com.yoodb.study.demo03.bean.BootUser; import com.yoodb.study.demo03.mapper.one.BootUserOneMapper; import com.yoodb.study.demo03.mapper.two.BootUserTwoMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class BootUserService { @Autowired private BootUserOneMapper onemapper; @Autowired private BootUserTwoMapper twomapper; public List<BootUser> getUsers(){ List<BootUser> listone = onemapper.selectAll(); List<BootUser> listtwo = twomapper.selectAll(); listone.addAll(listtwo); return listone; } }
4、建立controller類檔案
新增檔名BootUserController類檔案,具體程式碼如下:
package com.yoodb.study.demo03; import java.util.List; import com.yoodb.study.demo03.bean.BootUser; import com.yoodb.study.demo03.service.BootUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/myt") public class BootUserController { @Autowired private BootUserService service; @RequestMapping("/getUsers") public List<BootUser> getUsers() { List<BootUser> list = service.getUsers(); return list; } }
mapper的介面、xml檔案及實體檔案、service層、controller層建立完成後,目錄如圖:
5、專案啟動
以上操作完成後,專案啟動過程中控制檯報錯,錯誤資訊如下:
### Error querying database. Cause: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "BOOT_USER" not found; SQL statement: select id, user_name, detail from boot_user order by detail asc [42102-200] ### The error may exist in file [F:\project\study\springboot-study-demo03\target\classes\mapper\one\BootUserOneMapper.xml] ### The error may involve com.yoodb.study.demo03.mapper.one.BootUserOneMapper.selectAll ### The error occurred while executing a query ### SQL: select id, user_name, detail from boot_user order by detail asc ### Cause: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "BOOT_USER" not found; SQL statement:
問題分析:目前Spring Boot最新版本不會將application.properties檔案載入到classes目錄下,導致無法讀取到application.properties檔案資訊,從而在專案訪問的時候控制檯報上述錯誤資訊。
解決方法:就是在pom.xml檔案中增加如下配置資訊:
<resources> <resource> <!-- 指定resources外掛處理哪個目錄下的資原始檔 --> <directory>src/main/resources</directory> <includes> <include>**/**</include> </includes> </resource> </resources>
上述問題解決完後系統可以正常啟動,但是在通過瀏覽器訪問時還是會報錯。
訪問地址:
http://localhost:8080/myt/getUsers
錯誤資訊如下:
### Error querying database. Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required. ### The error may exist in file [F:\project\study\springboot-study-demo03\target\classes\mapper\one\BootUserOneMapper.xml] ### The error may involve com.yoodb.study.demo03.mapper.BootUserOneMapper.selectAll ### The error occurred while executing a query ### Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.] with root cause java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
問題分析:
1)在Spring Boot 1.* 版本中配置資料來源是spring.datasource.url和spring.datasource.driverClassName;
2)在Spring Boot 2.* 版本中配置資料來源是spring.datasource.jdbc-url和spring.datasource.driver-class-name;
3)Spring Boot專案中(本文是基於Spring Boot 2.3.0.M2 版本)由於無法讀取到application.properties檔案中“driver-class-name”和“jdbc-url”兩個引數導致專案報錯。
解決方法:
將application.properties檔案中spring.datasource..url和spring.datasource..driverClassName分別替換成spring.datasource..jdbc-url和spring.datasource..driver-class-name。
*Spring Boot原始碼分析:
1)spring.datasource.*.url替換成spring.datasource.*.jdbcUrl也沒有任何問題。
原始碼org.springframework.boot.jdbc包中DatabaseDriver.class檔案反編譯後,發現url引數必須以“jdbc”開始,具體原始碼如下:
public static DatabaseDriver fromJdbcUrl(String url) { if (StringUtils.hasLength(url)) { Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'"); String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(Locale.ENGLISH); DatabaseDriver[] var2 = values(); int var3 = var2.length; for(int var4 = 0; var4 < var3; ++var4) { DatabaseDriver driver = var2[var4]; Iterator var6 = driver.getUrlPrefixes().iterator(); while(var6.hasNext()) { String urlPrefix = (String)var6.next(); String prefix = ":" + urlPrefix + ":"; if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) { return driver; } } } } return UNKNOWN; }
2)spring.datasource.*.driverClassName不替換也沒有影響。
原始碼org.springframework.boot.autoconfigure.jdbc包中ConfigurationProperties.class檔案反編譯後,發現屬性即為driverClassName欄位,具體原始碼如下:
public String determineDriverClassName() { if (StringUtils.hasText(this.driverClassName)) { Assert.state(this.driverClassIsLoadable(), () -> { return "Cannot load driver class: " + this.driverClassName; }); return this.driverClassName; } else { ...
修改application.properties檔案配置,參考資訊如下:
spring.datasource.one.jdbc-url=jdbc:mysql://123.57.47.154:3306/springboot1 spring.datasource.one.username=root spring.datasource.one.password=wangyoodb spring.datasource.one.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.two.jdbc-url=jdbc:mysql://123.57.47.154:3306/springboot2 spring.datasource.two.username=root spring.datasource.two.password=wangyoodb spring.datasource.two.driver-class-name=com.mysql.cj.jdbc.Driver
引入話題
之前有群裡的網友問Spring Boot中jdbc-url和url有什麼區別?(題外話:關注微信公眾號“Java精選”,留言切換工會總好後臺傳送訊息可以進群,群內只允許探討技術,完全免費,幫助大家解決各位技術難題。)
相信現在通過上述的分析,大家應該很清楚了吧,兩者是因為Spring Boot升級版本調整了原始碼,所以“url”引數被重新命名了。
至此整合mybatis實現多資料來源的方法已完成,專案啟動成功後可以正常訪問,通過瀏覽器訪問輸出如下資訊:
[{"id":"1","name":"素文宅部落格","detail":"歡迎關注“Java精選”微信公眾號,專注程式設計師推送一些Java開發知識,包括基礎知識、各大流行框架(Mybatis、Spring、Spring Boot等)、大資料技術(Storm、Hadoop、MapReduce、Spark等)、資料庫(Mysql、Oracle、NoSQL等)、演算法與資料結構、面試專題、面試技巧經驗、職業規劃以及優質開源專案等。"},{"id":"2","name":"素文宅部落格導航","detail":"歡迎關注“Java精選”微信公眾號,一部分由小編總結整理,另一部分來源於網路上優質資源,希望對大家的學習和工作有所幫助。"}]
使用JdbcTemplate
1、新增controller類檔案
新增檔名JdbcTemplateController類檔案,具體程式碼如下:
package com.yoodb.study.demo03; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.yoodb.study.demo03.bean.BootUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping("/jte") public class JdbcTemplateController { @Autowired private JdbcTemplate oneSqlSessionTemplate; @Autowired private JdbcTemplate twoSqlSessionTemplate; @RequestMapping("/getOneUsers") public List<Map<String, Object>> getOneUsers(){ String sql = "select * from boot_user"; List<Map<String, Object>> list = twoSqlSessionTemplate.queryForList(sql); for (Map<String, Object> map : list) { Set<Entry<String, Object>> entries = map.entrySet( ); if(entries != null) { Iterator<Entry<String, Object>> iterator = entries.iterator( ); while(iterator.hasNext( )) { Entry<String, Object> entry =(Entry<String, Object>) iterator.next( ); Object key = entry.getKey( ); Object value = entry.getValue(); System.out.println(key+":"+value); } } } return list; } @RequestMapping("/getTwoUsers") public List<BootUser> getTwoUsers() { List<BootUser> list = twoSqlSessionTemplate.query("select id,user_name " + "name,detail from boot_user", new BeanPropertyRowMapper<>(BootUser.class)); return list; } }
多資料來源配置檔案、多資料來源類檔案、實體類檔案、controller層建立完成後,目錄如圖:
上述操作完成後,因為整合mybatis時部分程式碼已存在(配置多資料來源類檔案),所以使用JdbcTemplate配置完成。
2、專案啟動
專案啟動後訪問多資料來源一請求地址:
http://localhost:8080/jte/getOneUsers
通過瀏覽器訪問輸出如下資訊:
[{"id":1,"user_name":"素文宅部落格","password":"e10adc3949ba59abbe56e057f20f883e","role_name":"素文宅部落格","detail":"歡迎關注“Java精選”微信公眾號,專注程式設計師推送一些Java開發知識,包括基礎知識、各大流行框架(Mybatis、Spring、Spring Boot等)、大資料技術(Storm、Hadoop、MapReduce、Spark等)、資料庫(Mysql、Oracle、NoSQL等)、演算法與資料結構、面試專題、面試技巧經驗、職業規劃以及優質開源專案等。"}]
專案啟動後訪問多資料來源二請求地址:
http://localhost:8080/jte/getTwoUsers
通過瀏覽器訪問輸出如下資訊:
[{"id":"1","name":"素文宅部落格","detail":"歡迎關注“Java精選”微信公眾號,專注程式設計師推送一些Java開發知識,包括基礎知識、各大流行框架(Mybatis、Spring、Spring Boot等)、大資料技術(Storm、Hadoop、MapReduce、Spark等)、資料庫(Mysql、Oracle、NoSQL等)、演算法與資料結構、面試專題、面試技巧經驗、職業規劃以及優質開源專案等。"}]
Spring容器中JdbcTemplate提供了兩種注入方式,一種是使用@Resource註解,直接通過byName的方式注入進來,另外一種就是@Autowired註解加上@Qualifier註解,兩者聯合起來,實際上也是byName。
注意:
將JdbcTemplate注入成功後,oneSqlSessionTemplate和twoSqlSessionTemplate此時就代表操作不同的資料來源,使用不同的JdbcTemplate操作不同的資料來源,實現了多資料來源配置。
Spring Boot從入門到精通專案原始碼(多資料來源配置springboot-study-demo03)地址:
https://github.com/yoodb/springboot
至此,Spring Boot整合MyBatis和使用JdbcTemplate兩種方式多資料來源配置完成,下面大家有什麼問題歡迎留言評