mysql 多數據源訪問_通過Spring Boot配置動態數據源訪問多個數據庫的實現代碼

之前寫過一篇博客《Spring+Mybatis+Mysql搭建分布式數據庫訪問框架》描述如何通過Spring+Mybatis配置動態數據源訪問多個數據庫。但是之前的方案有一些限制(原博客中也描述了):只適用于數據庫數量不多且固定的情況。針對數據庫動態增加的情況無能為力。

下面講的方案能支持數據庫動態增刪,數量不限。

數據庫環境準備

下面一Mysql為例,先在本地建3個數據庫用于測試。需要說明的是本方案不限數據庫數量,支持不同的數據庫部署在不同的服務器上。如圖所示db_project_001、db_project_002、db_project_003。

471a2422016011635b24b3864774ad76.png

搭建Java后臺微服務項目

創建一個Spring Boot的maven項目:

94d3385f74618d36cff0a77ba18ee7cc.png

config:數據源配置管理類。

datasource:自己實現的數據源管理邏輯。

dbmgr:管理了項目編碼與數據庫IP、名稱的映射關系(實際項目中這部分數據保存在redis緩存中,可動態增刪)。

mapper:數據庫訪問接口。

model:映射模型。

rest:微服務對外發布的restful接口,這里用來測試。

application.yml:配置了數據庫的JDBC參數。

詳細的代碼實現

1. 添加數據源配置

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;

/**

* 數據源配置管理。

*

* @author elon

* @version 2018年2月26日

*/

@Configuration

@MapperScan(basePackages="com.elon.dds.mapper", value="sqlSessionFactory")

public class DataSourceConfig {

/**

* 根據配置參數創建數據源。使用派生的子類。

*

* @return 數據源

*/

@Bean(name="dataSource")

@ConfigurationProperties(prefix="spring.datasource")

public DataSource getDataSource() {

DataSourceBuilder builder = DataSourceBuilder.create();

builder.type(DynamicDataSource.class);

return builder.build();

}

/**

* 創建會話工廠。

*

* @param dataSource 數據源

* @return 會話工廠

*/

@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.定義動態數據源

1)? 首先增加一個數據庫標識類,用于區分不同的數據庫訪問。

由于我們為不同的project創建了單獨的數據庫,所以使用項目編碼作為數據庫的索引。而微服務支持多線程并發的,采用線程變量。

package com.elon.dds.datasource;

/**

* 數據庫標識管理類。用于區分數據源連接的不同數據庫。

*

* @author elon

* @version 2018-02-25

*/

public class DBIdentifier {

/**

* 用不同的工程編碼來區分數據庫

*/

private static ThreadLocal projectCode = new ThreadLocal();

public static String getProjectCode() {

return projectCode.get();

}

public static void setProjectCode(String code) {

projectCode.set(code);

}

}

2)? 從DataSource派生了一個DynamicDataSource,在其中實現數據庫連接的動態切換

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;

/**

* 定義動態數據源派生類。從基礎的DataSource派生,動態性自己實現。

*

* @author elon

* @version 2018-02-25

*/

public class DynamicDataSource extends DataSource {

private static Logger log = LogManager.getLogger(DynamicDataSource.class);

/**

* 改寫本方法是為了在請求不同工程的數據時去連接不同的數據庫。

*/

@Override

public Connection getConnection(){

String projectCode = DBIdentifier.getProjectCode();

//1、獲取數據源

DataSource dds = DDSHolder.instance().getDDS(projectCode);

//2、如果數據源不存在則創建

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;

}

}

/**

* 以當前數據對象作為模板復制一份。

*

* @return dds

* @throws IllegalAccessException

* @throws IllegalArgumentException

*/

private DataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {

DataSource dds = new DataSource();

// 2、復制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、設置數據庫名稱和IP(一般來說,端口和用戶名、密碼都是統一固定的)

String urlFormat = this.getUrl();

String url = String.format(urlFormat, ProjectDBMgr.instance().getDBIP(projectCode),

ProjectDBMgr.instance().getDBName(projectCode));

dds.setUrl(url);

return dds;

}

}

3)? 通過DDSTimer控制數據連接釋放(超過指定時間未使用的數據源釋放)

package com.elon.dds.datasource;

import org.apache.tomcat.jdbc.pool.DataSource;

/**

* 動態數據源定時器管理。長時間無訪問的數據庫連接關閉。

*

* @author elon

* @version 2018年2月25日

*/

public class DDSTimer {

/**

* 空閑時間周期。超過這個時長沒有訪問的數據庫連接將被釋放。默認為10分鐘。

*/

private static long idlePeriodTime = 10 * 60 * 1000;

/**

* 動態數據源

*/

private DataSource dds;

/**

* 上一次訪問的時間

*/

private long lastUseTime;

public DDSTimer(DataSource dds) {

this.dds = dds;

this.lastUseTime = System.currentTimeMillis();

}

/**

* 更新最近訪問時間

*/

public void refreshTime() {

lastUseTime = System.currentTimeMillis();

}

/**

* 檢測數據連接是否超時關閉。

*

* @return true-已超時關閉; false-未超時

*/

public boolean checkAndClose() {

if (System.currentTimeMillis() - lastUseTime > idlePeriodTime)

{

dds.close();

return true;

}

return false;

}

public DataSource getDds() {

return dds;

}

}

4)????? 增加DDSHolder來管理不同的數據源,提供數據源的添加、查詢功能

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;

/**

* 動態數據源管理器。

*

* @author elon

* @version 2018年2月25日

*/

public class DDSHolder {

/**

* 管理動態數據源列表。

*/

private Map ddsMap = new HashMap();

/**

* 通過定時任務周期性清除不使用的數據源

*/

private static Timer clearIdleTask = new Timer();

static {

clearIdleTask.schedule(new ClearIdleTimerTask(), 5000, 60 * 1000);

};

private DDSHolder() {

}

/*

* 獲取單例對象

*/

public static DDSHolder instance() {

return DDSHolderBuilder.instance;

}

/**

* 添加動態數據源。

*

* @param projectCode 項目編碼

* @param dds dds

*/

public synchronized void addDDS(String projectCode, DataSource dds) {

DDSTimer ddst = new DDSTimer(dds);

ddsMap.put(projectCode, ddst);

}

/**

* 查詢動態數據源

*

* @param projectCode 項目編碼

* @return dds

*/

public synchronized DataSource getDDS(String projectCode) {

if (ddsMap.containsKey(projectCode)) {

DDSTimer ddst = ddsMap.get(projectCode);

ddst.refreshTime();

return ddst.getDds();

}

return null;

}

/**

* 清除超時無人使用的數據源。

*/

public synchronized void clearIdleDDS() {

Iterator> iter = ddsMap.entrySet().iterator();

for (; iter.hasNext(); ) {

Entry entry = iter.next();

if (entry.getValue().checkAndClose())

{

iter.remove();

}

}

}

/**

* 單例構件類

* @author elon

* @version 2018年2月26日

*/

private static class DDSHolderBuilder {

private static DDSHolder instance = new DDSHolder();

}

}

5)????? 定時器任務ClearIdleTimerTask用于定時清除空閑的數據源

package com.elon.dds.datasource;

import java.util.TimerTask;

/**

* 清除空閑連接任務。

*

* @author elon

* @version 2018年2月26日

*/

public class ClearIdleTimerTask extends TimerTask {

@Override

public void run() {

DDSHolder.instance().clearIdleDDS();

}

}

3.?????? 管理項目編碼與數據庫IP和名稱的映射關系

package com.elon.dds.dbmgr;

import java.util.HashMap;

import java.util.Map;

/**

* 項目數據庫管理。提供根據項目編碼查詢數據庫名稱和IP的接口。

* @author elon

* @version 2018年2月25日

*/

public class ProjectDBMgr {

/**

* 保存項目編碼與數據名稱的映射關系。這里是硬編碼,實際開發中這個關系數據可以保存到redis緩存中;

* 新增一個項目或者刪除一個項目只需要更新緩存。到時這個類的接口只需要修改為從緩存拿數據。

*/

private Map dbNameMap = new HashMap();

/**

* 保存項目編碼與數據庫IP的映射關系。

*/

private Map dbIPMap = new HashMap();

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;

}

// 實際開發中改為從緩存獲取

public String getDBName(String projectCode) {

if (dbNameMap.containsKey(projectCode)) {

return dbNameMap.get(projectCode);

}

return "";

}

//實際開發中改為從緩存中獲取

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.?????? 定義數據庫訪問的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

{

/**

* 查詢所有用戶數據

* @return 用戶數據列表

*/

@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 getUsers();

}

5.?????? 定義查詢對象模型

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.?????? 定義查詢用戶數據的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;

/**

* 用戶數據訪問接口。

*

* @author elon

* @version 2018年2月26日

*/

@RestController

@RequestMapping(value="/user")

public class WSUser {

@Autowired

private UserMapper userMapper;

/**

* 查詢項目中所有用戶信息

*

* @param projectCode 項目編碼

* @return 用戶列表

*/

@RequestMapping(value="/v1/users", method=RequestMethod.GET)

public List queryUser(@RequestParam(value="projectCode", required=true) String projectCode)

{

DBIdentifier.setProjectCode(projectCode);

return userMapper.getUsers();

}

}

要求每次查詢都要帶上projectCode參數。

7.?????? 編寫Spring Boot App的啟動代碼

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中配置數據源

其中的數據庫IP和數據庫名稱使用%s。在查詢用戶數據中動態切換。

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

測試方案

1.?????? 查詢project_001的數據,正常返回

3393b556794e5eb7e588474c3cb9de8f.png

2.?????? 查詢project_002的數據,正常返回

529d10427f70b920543d877a3d30a644.png

總結

以上所述是小編給大家介紹的通過Spring Boot配置動態數據源訪問多個數據庫的實現代碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/394278.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/394278.shtml
英文地址,請注明出處:http://en.pswp.cn/news/394278.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

我如何將Google I / O 2018的興奮帶給尼日利亞沃里的115個人

Google Developer Group Warri的第一個I / O擴展事件的故事 (A tale of Google Developer Group Warri’s first I/O Extended event) Google I/O is one of the largest developer festivals in the tech ecosystem. I am the lead organizer for the Google Developer Group …

菜鳥postman接口測試_postman 接口測試(轉)

本文轉載自testerhome;作者:xinxi1990 ;原文鏈接:https://testerhome.com/topics/18719;轉載以分享知識為目的,著作權歸原作者所有,如有侵權,請聯系刪除。postman使用創建用例集啟動…

求絕對值最小的數

題目 有一個升序排列的數組&#xff0c;數組中可能有正數&#xff0c;負數或0. 求數組中元素的絕對值最小的數. 例如 數組{-10&#xff0c; 05&#xff0c; 02 &#xff0c;7&#xff0c;15&#xff0c;50} 絕對值最小的是-2 解答 #include <bits/stdc.h> using namespac…

leetcode面試題 04.02. 最小高度樹(深度優先搜索)

給定一個有序整數數組&#xff0c;元素各不相同且按升序排列&#xff0c;編寫一個算法&#xff0c;創建一棵高度最小的二叉搜索樹。 public TreeNode sortedArrayToBST(int[] nums) {if(nums.length0) return null;return BST(nums,0,nums.length-1);}public TreeNode BST(int[…

IT團隊如何贏得尊重?

本文講的是IT團隊如何贏得尊重,在傳統觀念中&#xff0c;作為企業的IT人&#xff0c;似乎都有一種揮之不去的消極情緒&#xff1a;能夠為企業帶來直接利益的業務部門才是企業核心&#xff0c;而作為技術支撐的IT部門&#xff0c;則是作為附屬而存在。 我們經常也會聽到一些企業…

mysql 官方鏡像_運行官方mysql 鏡像

//目前最新的為mysql 8sudo docker run -itd --restart unless-stopped --nethost --name mysql -p3306:3306 -e MYSQL_ROOT_PASSWORDroot mysqlmysql 官方docker 需要重新設置密碼&#xff0c;否則無法遠程連接step1 : docker exec -it [容器id] /bin/bashstep2 : 登陸mysql &…

我如何使用React,Redux-Saga和Styled Components構建NBA球員資料獲取器

by Jonathan Puc喬納森普克(Jonathan Puc) 我如何使用React&#xff0c;Redux-Saga和Styled Components構建NBA球員資料獲取器 (How I built an NBA player profile fetcher with React, Redux-Saga, and Styled Components) Hello, all! It’s been a while since I built so…

vb 數組屬性_VB中菜單編輯器的使用講解及實際應用

大家好&#xff0c;今天我們共同來學習VB中菜單方面的知識。VB中菜單的基本作用有兩個&#xff1a;1、提供人機對話的界面&#xff0c;以便讓使用者選擇應用系統的各種功能&#xff1b;2、管理應用系統&#xff0c;控制各種功能模塊的運行。在實際應用中&#xff0c;菜單可分為…

《JAVA程序設計》_第七周學習總結

一、學習內容 1.String類——8,1知識 Java專門提供了用來處理字符序列的String類。String類在java.lang包中&#xff0c;由于java.lang包中的類被默認引入&#xff0c;因此程序可以直接使用String類。需要注意的是Java把String類聲明為final類&#xff0c;因此用戶不能擴展Stri…

leetcode109. 有序鏈表轉換二叉搜索樹(深度優先搜索/快慢指針)

給定一個單鏈表&#xff0c;其中的元素按升序排序&#xff0c;將其轉換為高度平衡的二叉搜索樹。 本題中&#xff0c;一個高度平衡二叉樹是指一個二叉樹每個節點 的左右兩個子樹的高度差的絕對值不超過 1。 解題思路 先將鏈表轉換成數組&#xff0c;再構造二叉搜索樹 代碼 …

NeHe OpenGL教程 第三十七課:卡通映射

轉自【翻譯】NeHe OpenGL 教程 前言 聲明&#xff0c;此 NeHe OpenGL教程系列文章由51博客yarin翻譯&#xff08;2010-08-19&#xff09;&#xff0c;本博客為轉載并稍加整理與修改。對NeHe的OpenGL管線教程的編寫&#xff0c;以及yarn的翻譯整理表示感謝。 NeHe OpenGL第三十七…

SDN交換機在云計算網絡中的應用場景

SDN的技術已經發展了好幾年了&#xff0c;而云計算的歷史更長&#xff0c;兩者的結合更是作為SDN的一個殺手級應用在近兩年炒得火熱&#xff0c;一些知名咨詢公司的關于SDN逐年增加的市場份額的論斷&#xff0c;也主要是指SDN在云計算網絡中的應用。 關于SDN在云計算網絡中的應…

sql server 里面怎么支持數字使用雙引號_國查:用中文編寫SQL

這兩天被 文言(wenyan-lang)刷屏了&#xff0c;這個項目在于使用文言文進行編程&#xff0c;我打算蹭個熱度&#xff0c;把年初的作品再撈一撈&#xff0c;即中文SQL。1. 文言Wenyan&#xff1a;吾有一數。曰三。名之曰「甲」。為是「甲」遍。吾有一言。曰「「問天地好在。」」…

七日掌握設計配色基礎_掌握正確的基礎知識:如何設計網站的導航,搜索和首頁...

七日掌握設計配色基礎by Anant Jain通過Anant Jain 掌握正確的基礎知識&#xff1a;如何設計網站的導航&#xff0c;搜索和首頁 (Get the basics right: how to design your site’s navigation, search, and homepage) 一個7分鐘的指南&#xff0c;使這三個基礎組件正確無誤。…

python渲染光線_python模板渲染配置文件

python的mako、jinja2模板庫&#xff0c;確實好用&#xff01;這里做個筆記&#xff0c;好記性不如爛筆頭。#!/usr/bin/env python#encodingutf-8import sys,yaml # 配置文件使用yaml格式from mako.template import Template # 加載mako庫的Templat…

leetcode114. 二叉樹展開為鏈表(深度優先搜索)

給定一個二叉樹&#xff0c;原地將它展開為一個單鏈表。例如&#xff0c;給定二叉樹1/ \2 5/ \ \ 3 4 6 將其展開為&#xff1a;1\2\3\4\5\6代碼 class Solution {public void flatten(TreeNode root) {flat(root);}public TreeNode flat(TreeNode root) {if(rootnull)…

eclipse新建web項目

需要點擊File—>New—>Other…在Web文件夾下找到Dynamic Web Project—>Next修改server端口可以在啟動項目后訪問地址是端口號項目名轉載于:https://juejin.im/post/5cb4999df265da037b610545

idea tips

AltInsert 自動出現generate ,,里面有構造方法&#xff0c;getter,setter... CtrlO,重寫方法 CtrlI...自動出現接口的方法 轉載于:https://www.cnblogs.com/bin-lin/p/6247538.html

革新以太網交換機架構 全光網絡的風刮進園區

全光網絡的風正在刮進園區網&#xff0c;眾所周知&#xff0c;光纖入戶發展迅速&#xff0c;隨著PON&#xff08;無源光纖網絡&#xff09;技術在運營商通信網絡的大規模使用&#xff0c;PON相關產業鏈逐步成熟&#xff0c;這也使得PON技術逐步在企業園區網得到應用。 基于銅線…

mysql loop循環實例_MySql CURSOR+LOOP循環-使用小實例

轉載自https://blog.csdn.net/starinbrook/article/details/77078126轉載自https://blog.csdn.net/makang456/article/details/53896346/【簡介】游標實際上是一種能從包括多條數據記錄的結果集中每次提取一條記錄的機制。游標充當指針的作用。盡管游標能遍歷結果中的所有行&am…