您好,登錄后才能下訂單哦!
小編給大家分享一下如何通過Spring Boot配置動(dòng)態(tài)數(shù)據(jù)源訪問多個(gè)數(shù)據(jù)庫,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
數(shù)據(jù)庫環(huán)境準(zhǔn)備
下面一Mysql為例,先在本地建3個(gè)數(shù)據(jù)庫用于測(cè)試。需要說明的是本方案不限數(shù)據(jù)庫數(shù)量,支持不同的數(shù)據(jù)庫部署在不同的服務(wù)器上。如圖所示db_project_001、db_project_002、db_project_003。
搭建Java后臺(tái)微服務(wù)項(xiàng)目
創(chuàng)建一個(gè)Spring Boot的maven項(xiàng)目:
config:數(shù)據(jù)源配置管理類。
datasource:自己實(shí)現(xiàn)的數(shù)據(jù)源管理邏輯。
dbmgr:管理了項(xiàng)目編碼與數(shù)據(jù)庫IP、名稱的映射關(guān)系(實(shí)際項(xiàng)目中這部分?jǐn)?shù)據(jù)保存在redis緩存中,可動(dòng)態(tài)增刪)。
mapper:數(shù)據(jù)庫訪問接口。
model:映射模型。
rest:微服務(wù)對(duì)外發(fā)布的restful接口,這里用來測(cè)試。
application.yml:配置了數(shù)據(jù)庫的JDBC參數(shù)。
詳細(xì)的代碼實(shí)現(xiàn)
1. 添加數(shù)據(jù)源配置
package com.elon.dds.config; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.elon.dds.datasource.DynamicDataSource; /** * 數(shù)據(jù)源配置管理。 * * @author elon * @version 2018年2月26日 */ @Configuration @MapperScan(basePackages="com.elon.dds.mapper", value="sqlSessionFactory") public class DataSourceConfig { /** * 根據(jù)配置參數(shù)創(chuàng)建數(shù)據(jù)源。使用派生的子類。 * * @return 數(shù)據(jù)源 */ @Bean(name="dataSource") @ConfigurationProperties(prefix="spring.datasource") public DataSource getDataSource() { DataSourceBuilder builder = DataSourceBuilder.create(); builder.type(DynamicDataSource.class); return builder.build(); } /** * 創(chuàng)建會(huì)話工廠。 * * @param dataSource 數(shù)據(jù)源 * @return 會(huì)話工廠 */ @Bean(name="sqlSessionFactory") public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); try { return bean.getObject(); } catch (Exception e) { e.printStackTrace(); return null; } } }
2.定義動(dòng)態(tài)數(shù)據(jù)源
1) 首先增加一個(gè)數(shù)據(jù)庫標(biāo)識(shí)類,用于區(qū)分不同的數(shù)據(jù)庫訪問。
由于我們?yōu)椴煌膒roject創(chuàng)建了單獨(dú)的數(shù)據(jù)庫,所以使用項(xiàng)目編碼作為數(shù)據(jù)庫的索引。而微服務(wù)支持多線程并發(fā)的,采用線程變量。
package com.elon.dds.datasource; /** * 數(shù)據(jù)庫標(biāo)識(shí)管理類。用于區(qū)分?jǐn)?shù)據(jù)源連接的不同數(shù)據(jù)庫。 * * @author elon * @version 2018-02-25 */ public class DBIdentifier { /** * 用不同的工程編碼來區(qū)分?jǐn)?shù)據(jù)庫 */ private static ThreadLocal<String> projectCode = new ThreadLocal<String>(); public static String getProjectCode() { return projectCode.get(); } public static void setProjectCode(String code) { projectCode.set(code); } }
2) 從DataSource派生了一個(gè)DynamicDataSource,在其中實(shí)現(xiàn)數(shù)據(jù)庫連接的動(dòng)態(tài)切換
import java.lang.reflect.Field; import java.sql.Connection; import java.sql.SQLException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tomcat.jdbc.pool.DataSource; import org.apache.tomcat.jdbc.pool.PoolProperties; import com.elon.dds.dbmgr.ProjectDBMgr; /** * 定義動(dòng)態(tài)數(shù)據(jù)源派生類。從基礎(chǔ)的DataSource派生,動(dòng)態(tài)性自己實(shí)現(xiàn)。 * * @author elon * @version 2018-02-25 */ public class DynamicDataSource extends DataSource { private static Logger log = LogManager.getLogger(DynamicDataSource.class); /** * 改寫本方法是為了在請(qǐng)求不同工程的數(shù)據(jù)時(shí)去連接不同的數(shù)據(jù)庫。 */ @Override public Connection getConnection(){ String projectCode = DBIdentifier.getProjectCode(); //1、獲取數(shù)據(jù)源 DataSource dds = DDSHolder.instance().getDDS(projectCode); //2、如果數(shù)據(jù)源不存在則創(chuàng)建 if (dds == null) { try { DataSource newDDS = initDDS(projectCode); DDSHolder.instance().addDDS(projectCode, newDDS); } catch (IllegalArgumentException | IllegalAccessException e) { log.error("Init data source fail. projectCode:" + projectCode); return null; } } dds = DDSHolder.instance().getDDS(projectCode); try { return dds.getConnection(); } catch (SQLException e) { e.printStackTrace(); return null; } } /** * 以當(dāng)前數(shù)據(jù)對(duì)象作為模板復(fù)制一份。 * * @return dds * @throws IllegalAccessException * @throws IllegalArgumentException */ private DataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException { DataSource dds = new DataSource(); // 2、復(fù)制PoolConfiguration的屬性 PoolProperties property = new PoolProperties(); Field[] pfields = PoolProperties.class.getDeclaredFields(); for (Field f : pfields) { f.setAccessible(true); Object value = f.get(this.getPoolProperties()); try { f.set(property, value); } catch (Exception e) { log.info("Set value fail. attr name:" + f.getName()); continue; } } dds.setPoolProperties(property); // 3、設(shè)置數(shù)據(jù)庫名稱和IP(一般來說,端口和用戶名、密碼都是統(tǒng)一固定的) String urlFormat = this.getUrl(); String url = String.format(urlFormat, ProjectDBMgr.instance().getDBIP(projectCode), ProjectDBMgr.instance().getDBName(projectCode)); dds.setUrl(url); return dds; } }
3) 通過DDSTimer控制數(shù)據(jù)連接釋放(超過指定時(shí)間未使用的數(shù)據(jù)源釋放)
package com.elon.dds.datasource; import org.apache.tomcat.jdbc.pool.DataSource; /** * 動(dòng)態(tài)數(shù)據(jù)源定時(shí)器管理。長時(shí)間無訪問的數(shù)據(jù)庫連接關(guān)閉。 * * @author elon * @version 2018年2月25日 */ public class DDSTimer { /** * 空閑時(shí)間周期。超過這個(gè)時(shí)長沒有訪問的數(shù)據(jù)庫連接將被釋放。默認(rèn)為10分鐘。 */ private static long idlePeriodTime = 10 * 60 * 1000; /** * 動(dòng)態(tài)數(shù)據(jù)源 */ private DataSource dds; /** * 上一次訪問的時(shí)間 */ private long lastUseTime; public DDSTimer(DataSource dds) { this.dds = dds; this.lastUseTime = System.currentTimeMillis(); } /** * 更新最近訪問時(shí)間 */ public void refreshTime() { lastUseTime = System.currentTimeMillis(); } /** * 檢測(cè)數(shù)據(jù)連接是否超時(shí)關(guān)閉。 * * @return true-已超時(shí)關(guān)閉; false-未超時(shí) */ public boolean checkAndClose() { if (System.currentTimeMillis() - lastUseTime > idlePeriodTime) { dds.close(); return true; } return false; } public DataSource getDds() { return dds; } }
4) 增加DDSHolder來管理不同的數(shù)據(jù)源,提供數(shù)據(jù)源的添加、查詢功能
package com.elon.dds.datasource; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Timer; import org.apache.tomcat.jdbc.pool.DataSource; /** * 動(dòng)態(tài)數(shù)據(jù)源管理器。 * * @author elon * @version 2018年2月25日 */ public class DDSHolder { /** * 管理動(dòng)態(tài)數(shù)據(jù)源列表。<工程編碼,數(shù)據(jù)源> */ private Map<String, DDSTimer> ddsMap = new HashMap<String, DDSTimer>(); /** * 通過定時(shí)任務(wù)周期性清除不使用的數(shù)據(jù)源 */ private static Timer clearIdleTask = new Timer(); static { clearIdleTask.schedule(new ClearIdleTimerTask(), 5000, 60 * 1000); }; private DDSHolder() { } /* * 獲取單例對(duì)象 */ public static DDSHolder instance() { return DDSHolderBuilder.instance; } /** * 添加動(dòng)態(tài)數(shù)據(jù)源。 * * @param projectCode 項(xiàng)目編碼 * @param dds dds */ public synchronized void addDDS(String projectCode, DataSource dds) { DDSTimer ddst = new DDSTimer(dds); ddsMap.put(projectCode, ddst); } /** * 查詢動(dòng)態(tài)數(shù)據(jù)源 * * @param projectCode 項(xiàng)目編碼 * @return dds */ public synchronized DataSource getDDS(String projectCode) { if (ddsMap.containsKey(projectCode)) { DDSTimer ddst = ddsMap.get(projectCode); ddst.refreshTime(); return ddst.getDds(); } return null; } /** * 清除超時(shí)無人使用的數(shù)據(jù)源。 */ public synchronized void clearIdleDDS() { Iterator<Entry<String, DDSTimer>> iter = ddsMap.entrySet().iterator(); for (; iter.hasNext(); ) { Entry<String, DDSTimer> entry = iter.next(); if (entry.getValue().checkAndClose()) { iter.remove(); } } } /** * 單例構(gòu)件類 * @author elon * @version 2018年2月26日 */ private static class DDSHolderBuilder { private static DDSHolder instance = new DDSHolder(); } }
5) 定時(shí)器任務(wù)ClearIdleTimerTask用于定時(shí)清除空閑的數(shù)據(jù)源
package com.elon.dds.datasource; import java.util.TimerTask; /** * 清除空閑連接任務(wù)。 * * @author elon * @version 2018年2月26日 */ public class ClearIdleTimerTask extends TimerTask { @Override public void run() { DDSHolder.instance().clearIdleDDS(); } }
3. 管理項(xiàng)目編碼與數(shù)據(jù)庫IP和名稱的映射關(guān)系
package com.elon.dds.dbmgr; import java.util.HashMap; import java.util.Map; /** * 項(xiàng)目數(shù)據(jù)庫管理。提供根據(jù)項(xiàng)目編碼查詢數(shù)據(jù)庫名稱和IP的接口。 * @author elon * @version 2018年2月25日 */ public class ProjectDBMgr { /** * 保存項(xiàng)目編碼與數(shù)據(jù)名稱的映射關(guān)系。這里是硬編碼,實(shí)際開發(fā)中這個(gè)關(guān)系數(shù)據(jù)可以保存到redis緩存中; * 新增一個(gè)項(xiàng)目或者刪除一個(gè)項(xiàng)目只需要更新緩存。到時(shí)這個(gè)類的接口只需要修改為從緩存拿數(shù)據(jù)。 */ private Map<String, String> dbNameMap = new HashMap<String, String>(); /** * 保存項(xiàng)目編碼與數(shù)據(jù)庫IP的映射關(guān)系。 */ private Map<String, String> dbIPMap = new HashMap<String, String>(); private ProjectDBMgr() { dbNameMap.put("project_001", "db_project_001"); dbNameMap.put("project_002", "db_project_002"); dbNameMap.put("project_003", "db_project_003"); dbIPMap.put("project_001", "127.0.0.1"); dbIPMap.put("project_002", "127.0.0.1"); dbIPMap.put("project_003", "127.0.0.1"); } public static ProjectDBMgr instance() { return ProjectDBMgrBuilder.instance; } // 實(shí)際開發(fā)中改為從緩存獲取 public String getDBName(String projectCode) { if (dbNameMap.containsKey(projectCode)) { return dbNameMap.get(projectCode); } return ""; } //實(shí)際開發(fā)中改為從緩存中獲取 public String getDBIP(String projectCode) { if (dbIPMap.containsKey(projectCode)) { return dbIPMap.get(projectCode); } return ""; } private static class ProjectDBMgrBuilder { private static ProjectDBMgr instance = new ProjectDBMgr(); } }
4. 定義數(shù)據(jù)庫訪問的mapper
package com.elon.dds.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import com.elon.dds.model.User; /** * Mybatis映射接口定義。 * * @author elon * @version 2018年2月26日 */ @Mapper public interface UserMapper { /** * 查詢所有用戶數(shù)據(jù) * @return 用戶數(shù)據(jù)列表 */ @Results(value= { @Result(property="userId", column="id"), @Result(property="name", column="name"), @Result(property="age", column="age") }) @Select("select id, name, age from tbl_user") List<User> getUsers(); }
5. 定義查詢對(duì)象模型
package com.elon.dds.model; public class User { private int userId = -1; private String name = ""; private int age = -1; @Override public String toString() { return "name:" + name + "|age:" + age; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
6. 定義查詢用戶數(shù)據(jù)的restful接口
package com.elon.dds.rest; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.elon.dds.datasource.DBIdentifier; import com.elon.dds.mapper.UserMapper; import com.elon.dds.model.User; /** * 用戶數(shù)據(jù)訪問接口。 * * @author elon * @version 2018年2月26日 */ @RestController @RequestMapping(value="/user") public class WSUser { @Autowired private UserMapper userMapper; /** * 查詢項(xiàng)目中所有用戶信息 * * @param projectCode 項(xiàng)目編碼 * @return 用戶列表 */ @RequestMapping(value="/v1/users", method=RequestMethod.GET) public List<User> queryUser(@RequestParam(value="projectCode", required=true) String projectCode) { DBIdentifier.setProjectCode(projectCode); return userMapper.getUsers(); } }
要求每次查詢都要帶上projectCode參數(shù)。
7. 編寫Spring Boot App的啟動(dòng)代碼
package com.elon.dds; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Hello world! * */ @SpringBootApplication public class App { public static void main( String[] args ) { System.out.println( "Hello World!" ); SpringApplication.run(App.class, args); } }
8. 在application.yml中配置數(shù)據(jù)源
其中的數(shù)據(jù)庫IP和數(shù)據(jù)庫名稱使用%s。在查詢用戶數(shù)據(jù)中動(dòng)態(tài)切換。
spring: datasource: url: jdbc:mysql://%s:3306/%s?useUnicode=true&characterEncoding=utf-8 username: root password: driver-class-name: com.mysql.jdbc.Driver logging: config: classpath:log4j2.xml
測(cè)試方案
1. 查詢project_001的數(shù)據(jù),正常返回
2. 查詢project_002的數(shù)據(jù),正常返回
看完了這篇文章,相信你對(duì)“如何通過Spring Boot配置動(dòng)態(tài)數(shù)據(jù)源訪問多個(gè)數(shù)據(jù)庫”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。