動態(tài)配置多數(shù)據(jù)源-從數(shù)據(jù)庫讀取連接信息

轉載網友的,文章附原文鏈接,個人找了很久沒有找到好的方法解決,
配置多個數(shù)據(jù)源,每次有新的項目,必須修改程序配置文件,
使用原始的JDBC有太繁瑣,
轉載過來大家一起學習,交流!
需要我的demo的也可以聯(lián)系!

一. 背景
公司要求開發(fā)一個接口平臺,作為我司各系統(tǒng)之間,或我司系統(tǒng)與第三方系統(tǒng)的對接工具。使用微服務架構,通過該接口平臺可動態(tài)定義并生成restful接口。

二. 思考
系統(tǒng)之間的對接,無外乎就是把己方系統(tǒng)的數(shù)據(jù)發(fā)送給第三方或接收第三方系統(tǒng)發(fā)送過來的數(shù)據(jù)(加密簽名等機制本文不談)。作為一個支持動態(tài)定義接口的平臺,應該有自己的數(shù)據(jù)庫設計,它不應該和業(yè)務系統(tǒng)的耦合度太高。因此,最終決定方案如下:

本平臺獨立運行且有自己獨立的數(shù)據(jù)庫,即不能把本平臺的表建立在業(yè)務系統(tǒng)的庫里面;
可登陸平臺創(chuàng)建數(shù)據(jù)源信息(數(shù)據(jù)庫連接信息),并給每個數(shù)據(jù)源分配一個唯一的code,支持Oracle、Mysql、SQLServer三種數(shù)據(jù)庫類型;
數(shù)據(jù)源創(chuàng)建成功后,可以針對該數(shù)據(jù)源定義增刪改查接口,分別對應restful的post、delete、put、get請求;
接口定義完成后,自動生成接口地址,平臺接收到調用者的請求后,從請求URL(接口地址)解析出要操作的數(shù)據(jù)源code,然后基于Durid創(chuàng)建對應的數(shù)據(jù)庫連接池(由于創(chuàng)建數(shù)據(jù)庫連接池是比較費時的操作,因此僅當該數(shù)據(jù)源第一次被使用時才創(chuàng)建它的連接池,后期不會重復創(chuàng)建),連接池創(chuàng)建完成后再繼續(xù)后續(xù)的操作。
三. 實現(xiàn)
為了方便區(qū)分,我們把平臺自身的數(shù)據(jù)源稱為“主數(shù)據(jù)源”,動態(tài)創(chuàng)建的數(shù)據(jù)源稱為“客數(shù)據(jù)源”。

    第1步:在application.properties配置主數(shù)據(jù)源信息

   spring.datasource.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
   spring.datasource.username=adi
   spring.datasource.password=adipassword
   spring.datasource.driverClassName = oracle.jdbc.OracleDriver

   spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
   spring.datasource.initialSize=20
   spring.datasource.minIdle=10
   spring.datasource.maxActive=50
   spring.datasource.maxWait=60000



  第2步:創(chuàng)建一個數(shù)據(jù)源實體類DataSource(類名可以改)

  該類的關鍵字段:

  url:數(shù)據(jù)庫地址

  userName:數(shù)據(jù)庫用戶名

  passWord:數(shù)據(jù)庫密碼

  code:數(shù)據(jù)源編碼,保證唯一

  databasetype:數(shù)據(jù)庫類型,支持oracle、mysql、sqlserver2000、sqlserver



   第3步:創(chuàng)建動態(tài)數(shù)據(jù)源類DynamicDataSource(類名可以改)

  注意:這里的動態(tài)數(shù)據(jù)源不是“客數(shù)據(jù)源”,動態(tài)數(shù)據(jù)源類有一個“袋子”,用來裝具體的數(shù)據(jù)源對象,也就是說可以裝主數(shù)據(jù)源對象和各個客數(shù)據(jù)源對象。

 該類必須繼承AbstractRoutingDataSource,其中的奧妙請參考

http://blog.csdn.net/rj042/article/details/21654627 (感謝這位博主!向您學習了不少?。?/p>

代碼:

package com.bitservice.adi.datasource;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Map;
import java.util.Set;

import javax.sql.DataSource;

import org.apache.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.bitservice.adi.exception.ADIException;
import com.bitservice.adi.security.Base64;
import com.bitservice.adi.security.SecurityTools;
import com.bitservice.adi.util.ADIPropUtil;
import com.bitservice.adi.util.DBUtil;
import com.bitservice.adi.util.NullUtil;

public class DynamicDataSource extends AbstractRoutingDataSource {
private boolean debug = false;
private final Logger log = Logger.getLogger(getClass());
private Map<Object, Object> dynamicTargetDataSources;
private Object dynamicDefaultTargetDataSource;
@Override
protected Object determineCurrentLookupKey() {
String datasource = DBContextHolder.getDataSource();
if (debug) {
if (NullUtil.IsAllNotNullOfString(datasource)) {
Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
if (dynamicTargetDataSources2.containsKey(datasource)) {
log.info("---當前數(shù)據(jù)源:" + datasource + "---");
} else {
throw new ADIException("不存在的數(shù)據(jù)源:"+datasource,500);
}
} else {
log.info("---當前數(shù)據(jù)源:默認數(shù)據(jù)源---");
}
}
return datasource;
}

@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
    super.setTargetDataSources(targetDataSources);
    this.dynamicTargetDataSources = targetDataSources;
}
// 創(chuàng)建數(shù)據(jù)源
public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {
    try {
        try { // 排除連接不上的錯誤
            Class.forName(driveClass);
            DriverManager.getConnection(url, username, password);// 相當于連接數(shù)據(jù)庫
        } catch (Exception e) {
            return false;
        }
        @SuppressWarnings("resource")
        DruidDataSource druidDataSource = new DruidDataSource();
        
        druidDataSource.setName(key);
        druidDataSource.setDriverClassName(driveClass);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        druidDataSource.setInitialSize(50); //初始化時建立物理連接的個數(shù)。初始化發(fā)生在顯示調用init方法,或者第一次getConnection時
        druidDataSource.setMaxActive(200); //最大連接池數(shù)量
        druidDataSource.setMaxWait(60000); //獲取連接時最大等待時間,單位毫秒。當鏈接數(shù)已經達到了最大鏈接數(shù)的時候,應用如果還要獲取鏈接就會出現(xiàn)等待的現(xiàn)象,等待鏈接釋放并回到鏈接池,如果等待的時間過長就應該踢掉這個等待,不然應用很可能出現(xiàn)雪崩現(xiàn)象
        druidDataSource.setMinIdle(40); //最小連接池數(shù)量
        String validationQuery = "select 1 from dual";
        if("mysql".equalsIgnoreCase(databasetype)) {
            driveClass = DBUtil.mysqldriver;
            validationQuery = "select 1";
        } else if("oracle".equalsIgnoreCase(databasetype)){
            driveClass = DBUtil.oracledriver;
            druidDataSource.setPoolPreparedStatements(true); //是否緩存preparedStatement,也就是PSCache。PSCache對支持游標的數(shù)據(jù)庫性能提升巨大,比如說oracle。在mysql下建議關閉。
            druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(50);
            int sqlQueryTimeout = ADIPropUtil.sqlQueryTimeOut();
            druidDataSource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout="+sqlQueryTimeout);//對于耗時長的查詢sql,會受限于ReadTimeout的控制,單位毫秒
        } else if("sqlserver2000".equalsIgnoreCase(databasetype)){
            driveClass = DBUtil.sql2000driver;
            validationQuery = "select 1";
        } else if("sqlserver".equalsIgnoreCase(databasetype)){
            driveClass = DBUtil.sql2005driver;
            validationQuery = "select 1";
        }
        
        druidDataSource.setTestOnBorrow(true); //申請連接時執(zhí)行validationQuery檢測連接是否有效,這里建議配置為TRUE,防止取到的連接不可用
        druidDataSource.setTestWhileIdle(true);//建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。
        druidDataSource.setValidationQuery(validationQuery); //用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
        druidDataSource.setFilters("stat");//屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有:監(jiān)控統(tǒng)計用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
        druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
        druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一個連接在池中最小生存的時間,單位是毫秒,這里配置為3分鐘180000
        druidDataSource.setKeepAlive(true); //打開druid.keepAlive之后,當連接池空閑時,池中的minIdle數(shù)量以內的連接,空閑時間超過minEvictableIdleTimeMillis,則會執(zhí)行keepAlive操作,即執(zhí)行druid.validationQuery指定的查詢SQL,一般為select * from dual,只要minEvictableIdleTimeMillis設置的小于防火墻切斷連接時間,就可以保證當連接空閑時自動做?;顧z測,不會被防火墻切斷
        
        druidDataSource.setRemoveAbandoned(true); //是否移除泄露的連接/超過時間限制是否回收。
        druidDataSource.setRemoveAbandonedTimeout(3600); //泄露連接的定義時間(要超過最大事務的處理時間);單位為秒。這里配置為1小時
        druidDataSource.setLogAbandoned(true); ////移除泄露連接發(fā)生是是否記錄日志
        
        DataSource createDataSource = (DataSource) druidDataSource;
        druidDataSource.init();
        Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        dynamicTargetDataSources2.put(key, createDataSource);// 加入map
        setTargetDataSources(dynamicTargetDataSources2);// 將map賦值給父類的TargetDataSources
        super.afterPropertiesSet();// 將TargetDataSources中的連接信息放入resolvedDataSources管理
        log.info(key+"數(shù)據(jù)源初始化成功");
        //log.info(key+"數(shù)據(jù)源的概況:"+druidDataSource.dump());
        return true;
    } catch (Exception e) {
        log.error(e + "");
        return false;
    }
}
// 刪除數(shù)據(jù)源
public boolean delDatasources(String datasourceid) {
    Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
    if (dynamicTargetDataSources2.containsKey(datasourceid)) {
        Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
        for (DruidDataSource l : druidDataSourceInstances) {
            if (datasourceid.equals(l.getName())) {
                dynamicTargetDataSources2.remove(datasourceid);
                DruidDataSourceStatManager.removeDataSource(l);
                setTargetDataSources(dynamicTargetDataSources2);// 將map賦值給父類的TargetDataSources
                super.afterPropertiesSet();// 將TargetDataSources中的連接信息放入resolvedDataSources管理
                return true;
            }
        }
        return false;
    } else {
        return false;
    }
}

// 測試數(shù)據(jù)源連接是否有效
public boolean testDatasource(String key, String driveClass, String url, String username, String password) {
    try {
        Class.forName(driveClass);
        DriverManager.getConnection(url, username, password);
        return true;
    } catch (Exception e) {
        return false;
    }
}

/**
 * Specify the default target DataSource, if any.
 * <p>
 * The mapped value can either be a corresponding
 * {@link javax.sql.DataSource} instance or a data source name String (to be
 * resolved via a {@link #setDataSourceLookup DataSourceLookup}).
 * <p>
 * This DataSource will be used as target if none of the keyed
 * {@link #setTargetDataSources targetDataSources} match the
 * {@link #determineCurrentLookupKey()} current lookup key.
 */
@Override
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
    super.setDefaultTargetDataSource(defaultTargetDataSource);
    this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
}

/**
 * @param debug
 *            the debug to set
 */
public void setDebug(boolean debug) {
    this.debug = debug;
}

/**
 * @return the debug
 */
public boolean isDebug() {
    return debug;
}

/**
 * @return the dynamicTargetDataSources
 */
public Map<Object, Object> getDynamicTargetDataSources() {
    return dynamicTargetDataSources;
}

/**
 * @param dynamicTargetDataSources
 *            the dynamicTargetDataSources to set
 */
public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) {
    this.dynamicTargetDataSources = dynamicTargetDataSources;
}

/**
 * @return the dynamicDefaultTargetDataSource
 */
public Object getDynamicDefaultTargetDataSource() {
    return dynamicDefaultTargetDataSource;
}

/**
 * @param dynamicDefaultTargetDataSource
 *            the dynamicDefaultTargetDataSource to set
 */
public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) {
    this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource;
}

public void createDataSourceWithCheck(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
    String datasourceId = dataSource.getDatasourceId();
    log.info("準備創(chuàng)建數(shù)據(jù)源"+datasourceId);
    Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
    if (dynamicTargetDataSources2.containsKey(datasourceId)) {
        log.info("數(shù)據(jù)源"+datasourceId+"之前已經創(chuàng)建,準備測試數(shù)據(jù)源是否正常...");
        //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);
        DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId);
        boolean rightFlag = true;
        Connection connection = null;
        try {

// log.info(datasourceId+"數(shù)據(jù)源的概況->當前閑置連接數(shù):"+druidDataSource.getPoolingCount());
// long activeCount = druidDataSource.getActiveCount();
// log.info(datasourceId+"數(shù)據(jù)源的概況->當前活動連接數(shù):"+activeCount);
// if(activeCount > 0) {
// log.info(datasourceId+"數(shù)據(jù)源的概況->活躍連接堆棧信息:"+druidDataSource.getActiveConnectionStackTrace());
// }
log.info("準備獲取數(shù)據(jù)庫連接...");
connection = druidDataSource.getConnection();
log.info("數(shù)據(jù)源"+datasourceId+"正常");
} catch (Exception e) {
log.error(e.getMessage(),e); //把異常信息打印到日志文件
rightFlag = false;
log.info("緩存數(shù)據(jù)源"+datasourceId+"已失效,準備刪除...");
if(delDatasources(datasourceId)) {
log.info("緩存數(shù)據(jù)源刪除成功");
} else {
log.info("緩存數(shù)據(jù)源刪除失敗");
}
} finally {
if(null != connection) {
connection.close();
}
}
if(rightFlag) {
log.info("不需要重新創(chuàng)建數(shù)據(jù)源");
return;
} else {
log.info("準備重新創(chuàng)建數(shù)據(jù)源...");
createDataSource(dataSource);
log.info("重新創(chuàng)建數(shù)據(jù)源完成");
}
} else {
createDataSource(dataSource);
}

}

private  void createDataSource(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
    String datasourceId = dataSource.getDatasourceId();
    log.info("準備創(chuàng)建數(shù)據(jù)源"+datasourceId);
    String databasetype = dataSource.getDatabasetype();
    String username = dataSource.getUserName();
    String password = dataSource.getPassWord();
    password = new String(SecurityTools.decrypt(Base64.decode(password)));
    String url = dataSource.getUrl();
    String driveClass = "";
    if("mysql".equalsIgnoreCase(databasetype)) {
        driveClass = DBUtil.mysqldriver;
    } else if("oracle".equalsIgnoreCase(databasetype)){
        driveClass = DBUtil.oracledriver;
    }  else if("sqlserver2000".equalsIgnoreCase(databasetype)){
        driveClass = DBUtil.sql2000driver;
    } else if("sqlserver".equalsIgnoreCase(databasetype)){
        driveClass = DBUtil.sql2005driver;
    }
    if(testDatasource(datasourceId,driveClass,url,username,password)) {
        boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype);
        if(!result) {
            throw new ADIException("數(shù)據(jù)源"+datasourceId+"配置正確,但是創(chuàng)建失敗",500);
        }
    } else {
        throw new ADIException("數(shù)據(jù)源配置有錯誤",500);
    }
}

}

   第4步:創(chuàng)建數(shù)據(jù)源配置類DruidDBConfig(類名可以改)

   該類在springboot啟動時就會實例化,主要功能是創(chuàng)建主數(shù)據(jù)源對象和第3步的動態(tài)數(shù)據(jù)源對象。動態(tài)數(shù)據(jù)源對象手上有一個“袋子”,用來裝具體的數(shù)據(jù)源對象,通過代碼可以看到,我把主數(shù)據(jù)源對象也放到了這個“袋子”里面。

代碼:

package com.bitservice.adi.config;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.alibaba.druid.pool.DruidDataSource;
import com.bitservice.adi.datasource.DynamicDataSource;

/**

  • DruidDBConfig類被@Configuration標注,用作配置信息; DataSource對象被@Bean聲明,為Spring容器所管理,

  • @Primary表示這里定義的DataSource將覆蓋其他來源的DataSource。

  • @author aiyo92
    */
    @Configuration
    @EnableTransactionManagement
    public class DruidDBConfig {

    private final Logger log = Logger.getLogger(getClass());

    // adi數(shù)據(jù)庫連接信息
    @Value("{spring.datasource.url}") private String dbUrl; @Value("{spring.datasource.username}")
    private String username;
    @Value("{spring.datasource.password}") private String password; @Value("{spring.datasource.driverClassName}")
    private String driverClassName;

    // 連接池連接信息
    @Value("{spring.datasource.initialSize}") private int initialSize; @Value("{spring.datasource.minIdle}")
    private int minIdle;
    @Value("{spring.datasource.maxActive}") private int maxActive; @Value("{spring.datasource.maxWait}")
    private int maxWait;

    @Bean // 聲明其為Bean實例
    @Primary // 在同樣的DataSource中,首先使用被標注的DataSource
    @Qualifier("adiDataSource")
    public DataSource dataSource() throws SQLException {
    DruidDataSource datasource = new DruidDataSource();
    // 基礎連接信息
    datasource.setUrl(this.dbUrl);
    datasource.setUsername(username);
    datasource.setPassword(password);
    datasource.setDriverClassName(driverClassName);
    // 連接池連接信息
    datasource.setInitialSize(initialSize);
    datasource.setMinIdle(minIdle);
    datasource.setMaxActive(maxActive);
    datasource.setMaxWait(maxWait);

     datasource.setPoolPreparedStatements(true); //是否緩存preparedStatement,也就是PSCache。PSCache對支持游標的數(shù)據(jù)庫性能提升巨大,比如說oracle。在mysql下建議關閉。
     datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
     datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//對于耗時長的查詢sql,會受限于ReadTimeout的控制,單位毫秒
     datasource.setTestOnBorrow(true); //申請連接時執(zhí)行validationQuery檢測連接是否有效,這里建議配置為TRUE,防止取到的連接不可用
     datasource.setTestWhileIdle(true);//建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。
     String validationQuery = "select 1 from dual";
     datasource.setValidationQuery(validationQuery); //用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
     datasource.setFilters("stat,wall");//屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有:監(jiān)控統(tǒng)計用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
     datasource.setTimeBetweenEvictionRunsMillis(60000); //配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
     datasource.setMinEvictableIdleTimeMillis(180000); //配置一個連接在池中最小生存的時間,單位是毫秒,這里配置為3分鐘180000
     datasource.setKeepAlive(true); //打開druid.keepAlive之后,當連接池空閑時,池中的minIdle數(shù)量以內的連接,空閑時間超過minEvictableIdleTimeMillis,則會執(zhí)行keepAlive操作,即執(zhí)行druid.validationQuery指定的查詢SQL,一般為select * from dual,只要minEvictableIdleTimeMillis設置的小于防火墻切斷連接時間,就可以保證當連接空閑時自動做?;顧z測,不會被防火墻切斷
     
     datasource.setRemoveAbandoned(true); //是否移除泄露的連接/超過時間限制是否回收。
     datasource.setRemoveAbandonedTimeout(3600); //泄露連接的定義時間(要超過最大事務的處理時間);單位為秒。這里配置為1小時
     datasource.setLogAbandoned(true); ////移除泄露連接發(fā)生是是否記錄日志
     return datasource;
    

    }

    @Bean(name = "dynamicDataSource")
    @Qualifier("dynamicDataSource")
    public DataSource dynamicDataSource() throws SQLException {
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    dynamicDataSource.setDebug(false);
    dynamicDataSource.setDefaultTargetDataSource(dataSource());
    Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
    targetDataSources.put("adiDataSource", dataSource());
    dynamicDataSource.setTargetDataSources(targetDataSources);
    return dynamicDataSource;
    }

    @Bean(name = "dynamicJdbcTemplate")
    @Qualifier("dynamicJdbcTemplate")
    public NamedParameterJdbcTemplate dynamicJdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
    return new NamedParameterJdbcTemplate(dataSource);
    }

    @Bean(name = "adiJdbcTemplate")
    @Qualifier("adiJdbcTemplate")
    @Primary
    public NamedParameterJdbcTemplate adiJdbcTemplate(@Qualifier("adiDataSource") DataSource dataSource) {
    return new NamedParameterJdbcTemplate(dataSource);
    }

    @Bean(name = "entityManagerFactory")
    @Qualifier("entityManagerFactory")
    @Primary
    public LocalContainerEntityManagerFactoryBean entityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException{
    LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dataSource()).packages("com.bitservice.adi.entity").build();
    Properties jpaProperties = new Properties();
    jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
    jpaProperties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
    jpaProperties.put("hibernate.connection.charSet", "utf-8");
    jpaProperties.put("hibernate.show_sql", "false");
    entityManagerFactory.setJpaProperties(jpaProperties);
    return entityManagerFactory;
    }

    @Bean(name = "entityManager")
    @Qualifier("entityManager")
    @Primary
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) throws SQLException{
    return entityManageFactory(builder).getObject().createEntityManager();
    }

@Bean(name = "dynamicEntityManageFactory")
@Qualifier("dynamicEntityManageFactory")
public LocalContainerEntityManagerFactoryBean dynamicEntityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException{
    LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dynamicDataSource()).packages("com.bitservice.dynamic.entity").build();
    Properties jpaProperties = new Properties();
    //jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
    jpaProperties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
    jpaProperties.put("hibernate.connection.charSet", "utf-8");
    jpaProperties.put("hibernate.show_sql", "false");
    entityManagerFactory.setJpaProperties(jpaProperties);
    return entityManagerFactory;
}

@Bean(name = "dynamicEntityManage")
@Qualifier("dynamicEntityManage")
public EntityManager dynamicEntityManage(EntityManagerFactoryBuilder builder) throws SQLException{
    return entityManageFactory(builder).getObject().createEntityManager();
}

}

第5步:創(chuàng)建數(shù)據(jù)源切換類DBContextHolder(類名可以改)

代碼:

package com.bitservice.adi.datasource;

/**

  • 數(shù)據(jù)源切換
  • @author aiyo92

*/
public class DBContextHolder {
// 對當前線程的操作-線程安全的
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

// 調用此方法,切換數(shù)據(jù)源
public static void setDataSource(String dataSource) {
    contextHolder.set(dataSource);
}

// 獲取數(shù)據(jù)源
public static String getDataSource() {
    return contextHolder.get();
}

// 刪除數(shù)據(jù)源
public static void clearDataSource() {
    contextHolder.remove();
}

}

核心代碼寫完了,接下來就是怎么用了!

創(chuàng)建一個DAO類SqlRepository,專門用來操作各個客數(shù)據(jù)源。

以實現(xiàn)對客數(shù)據(jù)源的查詢請求為例,假如平臺創(chuàng)建了多個數(shù)據(jù)源,并為每個數(shù)據(jù)源定義了SQL語句,由于平臺底層執(zhí)行SQL的方法是共用的(都在SqlRepository類里面),那么如何在執(zhí)行查詢方法之前動態(tài)切換要查詢的客數(shù)據(jù)源呢?上代碼!

package com.bitservice.adi.dao;

import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;

import com.bitservice.adi.datasource.DBContextHolder;
import com.bitservice.adi.datasource.DynamicDataSource;
import com.bitservice.adi.entity.DataSource;

@Service
@Transactional
public class SqlRepository {
private final Logger logger = Logger.getLogger(getClass());

@Autowired
@Qualifier("dynamicJdbcTemplate")
private NamedParameterJdbcTemplate jdbcTemplate;

@Autowired
@Qualifier("dynamicDataSource")
private DynamicDataSource dynamicDataSource;

@PersistenceContext(unitName = "dynamicEntityManageFactory")
private EntityManager entityManager;

private static boolean dynamicFlag = true;

public List<Map<String, Object>> doSelect(DataSource dataSource, String sql, Map<String, Object> params) throws Exception {
    if(dynamicFlag) {
        dynamicDataSource.createDataSourceWithCheck(dataSource);
        DBContextHolder.setDataSource(dataSource.getDatasourceId());
    }
    //logger.info("執(zhí)行sql查詢doSelect-sql:" + sql);
    logger.info("sql_params:" + params);
    List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql, params);
    logger.info("查詢數(shù)據(jù)庫結果doSelect-result:" + resultList.toString());
    return resultList;
}

}

我這里是手動切換的,大家可以使用AOP自動切換。

最后溫馨提示:以上我給出的只是核心代碼,大家直接粘貼使用的話肯定會報錯,報錯的地方大家一看就知道怎么改,所以你們只需要把報錯的代碼根據(jù)實際情況換成自己的代碼即可。如果有任何疑問,咱們可以隨時交流!

作者:aiyo92
來源:CSDN
原文:https://blog.csdn.net/aiyo92/article/details/86518217

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容