本方案不限資料庫數量完全動態配置,支持不同的資料庫部署在不同的服務器上,(mybatis-plus沒測驗,下個版本用oracle配的時候嘗試plus)
一 、這次我們使用Mysql,本地現在有兩個個資料庫用于測驗,
如圖 
二、下一步我們看一下Druid繼承關系

我們可以看到想要配置DataSource其實非常簡單,繼承DruidDataSource就可以呼叫
getConnection方法了
三、下面直接開始上配置(簡單的一個小例子其他的自己擴展吧)
#連接池 使用阿里的druid
spring:
application:
name: mall_user
datasource:
url: jdbc:mysql://${IP}:3306/${NAME}?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2b8&useSSL=false
username:
password:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
四、開始上代碼
1、資料源配置管理類(DataSourceConfig.java)
package com.taipingyang.config.datasource2;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
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.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 資料源配置管理,
* @author zz
* @date 2021/1/10 20:46
*/
@MapperScan(basePackages="com.taipingyang.Mapper", value="sqlSessionFactory")
@Configuration
public class DataSourceConfig {
/**
* 根據配置引數創建資料源,使用派生的子類,
*
* @return 資料源
*/
@Bean(name="dataSource")
@ConfigurationProperties(prefix="spring.datasource")
public DruidDataSource getDataSource() {DataSourceBuilder.create().type(DynamicDataSource.class).build();
return 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)首先增加一個資料庫標識類,用于區分不同的資料庫(DBIdentifier.java)
由于我們為不同的project創建了單獨的資料庫,所以使用專案編碼作為資料庫的索引,而微服務支持多執行緒并發的,采用執行緒變數,
package com.taipingyang.config.datasource2;
/**
* 資料庫標識管理類,用于區分資料源連接的不同資料庫,
*
* @author zz
* @version 2021/1/10 20:46
*/
public class DBIdentifier {
/**
* 用不同的工程編碼來區分資料庫
*/
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派生一個DynamicDataSource,在其中實作資料庫連接的動態切換(DynamicDataSource.java)
package com.taipingyang.config.datasource2;
import java.lang.reflect.Field;
import java.sql.SQLException;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.druid.pool.PoolableWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* 定義動態資料源派生類,從基礎的DataSource派生,動態性自己實作,
*
* @author zz
* @version 2021/1/10 20:46
*/
public class DynamicDataSource extends DruidDataSource {
private static Logger log = LogManager.getLogger(DynamicDataSource.class);
/**
* 改寫本方法是為了在請求不同工程的資料時去連接不同的資料庫, DruidDataSource getConnection
*/
@Override
public DruidPooledConnection getConnection() {
String projectCode = DBIdentifier.getProjectCode()==null?"project_001": DBIdentifier.getProjectCode();
//1、獲取資料源
DruidDataSource dds = DDSHolder.instance().getDDS(projectCode);
DruidDataSource ddsddn = null;
//2、如果資料源不存在則創建
if (dds == null) {
try {
DruidDataSource 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 DruidDataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {
DruidDataSource dds = new DruidDataSource();
String urlFormat = this.getUrl();
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("root");
String url =urlFormat.replace("${IP}", ProjectDBMgr.instance().getDBIP(projectCode)).replace("${NAME}",ProjectDBMgr.instance().getDBName(projectCode));
dataSource.setUrl(url);
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return dataSource;
}
}
(3)通過DDSTimer控制資料連接釋放(DDSTimer.java)
package com.taipingyang.config.datasource2;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 動態資料源定時器管理,長時間無訪問的資料庫連接關閉,
* @author zz
* @version 2021/1/10 20:46
*/
public class DDSTimer {
/**
* 空閑時間周期,超過這個時長沒有訪問的資料庫連接將被釋放,默認為10分鐘,
*/
private static long idlePeriodTime = 10 * 60 * 1000;
/**
* 動態資料源
*/
private DruidDataSource dds;
/**
* 上一次訪問的時間
*/
private long lastUseTime;
public DDSTimer(DruidDataSource 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 DruidDataSource getDds() {
return dds;
}
}
(4)通過DDSHolder來管理不同的資料源,提供資料源的添加、查詢功能(DDSHolder.java)
package com.taipingyang.config.datasource2;
import com.alibaba.druid.pool.DruidDataSource;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.Map.Entry;
/**
* 動態資料源管理器,
*
* @author zz
* @version 2021/1/10 20:46
*/
public class DDSHolder {
/**
* 管理動態資料源串列,<工程編碼,資料源>
*/
private Map<String, DDSTimer> ddsMap =new HashMap<String, DDSTimer>();
/**
* 通過定時任務周期性清除不使用的資料源
*/
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, DruidDataSource dds) {
DDSTimer ddst = new DDSTimer(dds);
ddsMap.put(projectCode, ddst);
}
/**
* 查詢動態資料源
*
* @param projectCode 專案編碼
* @return dds
*/
public synchronized DruidDataSource getDDS(String projectCode) {
if (ddsMap.containsKey(projectCode)) {
DDSTimer ddst = ddsMap.get(projectCode);
ddst.refreshTime();
return ddst.getDds();
}
return null;
}
/**
* 清除超時無人使用的資料源,
*/
public synchronized void clearIdleDDS() {
Iterator<Map.Entry<String, DDSTimer>> iter = ddsMap.entrySet().iterator();
while(iter.hasNext()){
Entry<String, DDSTimer> 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用于定時清除空閑的資料源(ClearIdleTimerTask.java)
package com.taipingyang.config.datasource2;
import java.util.TimerTask;
/**
* 清除空閑連接任務,
*
* @author zz
* @version 2021/1/10 20:46
*/
public class ClearIdleTimerTask extends TimerTask {
@Override
public void run() {
DDSHolder.instance().clearIdleDDS();
}
}
(6)管理專案編碼與資料庫IP和名稱的映射關系(ProjectDBMgr.java)
package com.taipingyang.config.datasource2;
import java.util.HashMap;
import java.util.Map;
/**
* 專案資料庫管理,提供根據專案編碼查詢資料庫名稱和IP的介面,
*
* @author zz
* @version 2021/1/10 20:46
*/
public class ProjectDBMgr {
/**
* 保存專案編碼與資料名稱的映射關系,這里是硬編碼,實際開發中這個關系資料可以保存到redis快取中;
* 新增一個專案或者洗掉一個專案只需要更新快取,到時這個類的介面只需要修改為從快取拿資料,
*/
private Map<String, String> dbNameMap =new HashMap<String, String>();
/**
* 保存專案編碼與資料庫IP的映射關系,
*/
private Map<String, String> dbIPMap =new HashMap<String, String>();
private ProjectDBMgr() {
dbNameMap.put("project_001", "annoyingly");
dbNameMap.put("project_002", "tpy");
dbIPMap.put("project_001", "127.0.0.1");
dbIPMap.put("project_002", "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();
}
}
以上就是Druid+mybatis動態資料源全部配置
下面看一下我寫的測驗類=》
package com.taipingyang.Mapper;
import com.taipingyang.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
@Select(" select * from tpy_user ")
List<User> testuser();
}
直接上controller
@RestController
public class userController {
@Autowired
private UserService userService;
@RequestMapping("user/testuser")
public List<User> testuser(String projectCode) {
DBIdentifier.setProjectCode(projectCode);
return userService.testuser();
}
}
四、postman開始測驗
查詢資料庫全部正常


其實非常簡單,只需要在繼承DruidDataSource重新getConnection建立連接就好了,至于搞并發場景下還是需要憑借其他框架的,通過這次搭建希望大家能夠對DataSource有全新的認識,
代碼千萬行,注釋第一行,格式不規范,報錯兩行淚!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/247196.html
標籤:java
