SpringBoot+JPA+druid動態配置多數據源-從數據庫讀取連接信息

一. 需求採集

公司要求開發一個接口平臺,作爲我司各系統之間,或我司系統與第三方系統的對接工具。使用微服務架構,通過該接口平臺可動態定義並生成restful接口。

二. 需求分析

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

  1. 本平臺獨立運行且有自己獨立的數據庫,即不能把本平臺的表建立在業務系統的庫裏面;
  2. 可登陸平臺創建數據源信息(數據庫連接信息),並給每個數據源分配一個唯一的code,支持Oracle、Mysql、SQLServer三種數據庫類型;
  3. 數據源創建成功後,可以針對該數據源定義相關資源的增刪改查接口,分別對應restful的post、delete、put、get請求;
  4. 接口定義完成後,自動生成接口地址,平臺接收到調用者的請求後,從請求URL(接口地址)解析出要操作的數據源code,然後基於Durid創建對應的數據庫連接池(由於創建數據庫連接池是比較費時的操作,因此僅當該數據源第一次被使用時才創建它的連接池,後期不會重複創建),連接池創建完成後再繼續後續的操作。

三. 程序設計

爲了方便區分,我們把平臺自身的數據源稱爲“主數據源”,動態創建的數據源稱爲“客數據源”。
第1步:在application.properties配置主數據源信息

	   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步:創建一個數據源實體類com.bitservice.adi.entity.DataSource(類名可以改)
該類的關鍵字段:

      url:數據庫地址
      userName:數據庫用戶名
      passWord:數據庫密碼
      code:數據源編碼,保證唯一
      databasetype:數據庫類型,支持oracle、mysql、sqlserver2000、sqlserver

創建一個數據庫工具類DBUtil,可以用來在創建數據源的數據庫連接池之前判斷數據庫信息是否正確

package com.bitservice.adi.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.apache.log4j.Logger;

public class DBUtil {
	private final Logger logger = Logger.getLogger(getClass());
	private DatabaseType databaseType = null;
	private String username;
	private String password;
	private String url;
	public static final String mysqldriver = "com.mysql.jdbc.Driver"; // mysql數據庫的驅動類
	public static final String oracledriver = "oracle.jdbc.OracleDriver"; // oracles數據庫的驅動類
	public static final String sql2005driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; // sqlserver數據庫的驅動類
	public static final String sql2000driver = "net.sourceforge.jtds.jdbc.Driver"; // sqlserver數據庫的驅動類
	
	public DBUtil(DatabaseType databaseType, String username,
			String password,String url) {
		this.databaseType = databaseType;
		this.username = username;
		this.password = password;
		this.url = url;
		forName();
	}
	
	private void forName() {
		try {
			if (null == databaseType) {
				throw new RuntimeException("沒有指定數據庫類型");
			}
			if (databaseType == DatabaseType.MYSQL) {
				Class.forName(mysqldriver).newInstance();
			} else if (databaseType == DatabaseType.ORACLE) {
				Class.forName(oracledriver).newInstance();
			} else if (databaseType == DatabaseType.SQLSERVER2000) {
				Class.forName(sql2000driver).newInstance();
			} else if(databaseType == DatabaseType.SQLSERVER) {
				Class.forName(sql2005driver).newInstance();
			}
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
			throw new RuntimeException("加載數據庫驅動失敗");
		}
	}
	
	/**
	 * 如果程序發生異常,則表明無法連接
	 * @return
	 * @throws SQLException
	 */
	public Connection testConnection() throws SQLException {
		Connection conn = DriverManager.getConnection(
				url, username, password);// 獲取連接對象
		return conn;
	}
	
	public Connection getConnection() {
		try {
			Connection conn = DriverManager.getConnection(
					url, username, password);// 獲取連接對象
			return conn;
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public boolean connIsOk(Connection conn) throws SQLException {
		if (null != conn && !conn.isClosed()) {
			return true;
		}
		return false;
	}
	
	public void closeConn(Connection conn) {
		try {
			if (conn != null) {
				conn.close();
				conn = null;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	public enum DatabaseType {
		MYSQL, ORACLE, SQLSERVER2000, SQLSERVER
	}
}

第3步:創建動態數據源類DynamicDataSource(類名可以改)

注意:這裏的動態數據源不是“客數據源”,動態數據源類有一個“袋子”,用來裝具體的數據源對象,也就是說可以裝主數據源對象和各個客數據源對象。
該類必須繼承AbstractRoutingDataSource,其中的奧妙請參考
http://blog.csdn.net/rj042/article/details/21654627 (感謝這位博主!向您學習了不少!)

代碼:

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("---當前數據源:" + datasource + "---");
                } else {
                    throw new ADIException("不存在的數據源:"+datasource,500);
                }
            } else {
                log.info("---當前數據源:默認數據源---");
            }
        }
        return datasource;
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        this.dynamicTargetDataSources = targetDataSources;
    }
    // 創建數據源
    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);// 相當於連接數據庫
            } 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); //初始化時建立物理連接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時
            druidDataSource.setMaxActive(200); //最大連接池數量
            druidDataSource.setMaxWait(60000); //獲取連接時最大等待時間,單位毫秒。當鏈接數已經達到了最大鏈接數的時候,應用如果還要獲取鏈接就會出現等待的現象,等待鏈接釋放並回到鏈接池,如果等待的時間過長就應該踢掉這個等待,不然應用很可能出現雪崩現象
            druidDataSource.setMinIdle(40); //最小連接池數量
            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對支持遊標的數據庫性能提升巨大,比如說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); //申請連接時執行validationQuery檢測連接是否有效,這裏建議配置爲TRUE,防止取到的連接不可用
            druidDataSource.setTestWhileIdle(true);//建議配置爲true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
            druidDataSource.setValidationQuery(validationQuery); //用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery爲null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
            druidDataSource.setFilters("stat");//屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有:監控統計用的filter:stat日誌用的filter:log4j防禦sql注入的filter:wall
            druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
            druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一個連接在池中最小生存的時間,單位是毫秒,這裏配置爲3分鐘180000
            druidDataSource.setKeepAlive(true); //打開druid.keepAlive之後,當連接池空閒時,池中的minIdle數量以內的連接,空閒時間超過minEvictableIdleTimeMillis,則會執行keepAlive操作,即執行druid.validationQuery指定的查詢SQL,一般爲select * from dual,只要minEvictableIdleTimeMillis設置的小於防火牆切斷連接時間,就可以保證當連接空閒時自動做保活檢測,不會被防火牆切斷
            
            druidDataSource.setRemoveAbandoned(true); //是否移除泄露的連接/超過時間限制是否回收。
            druidDataSource.setRemoveAbandonedTimeout(3600); //泄露連接的定義時間(要超過最大事務的處理時間);單位爲秒。這裏配置爲1小時
            druidDataSource.setLogAbandoned(true); ////移除泄露連接發生是是否記錄日誌
            
            DataSource createDataSource = (DataSource) druidDataSource;
            druidDataSource.init();
            Map<Object, Object> dynamicTargetDataSources_temp = this.dynamicTargetDataSources;
            dynamicTargetDataSources_temp.put(key, createDataSource);// 加入map
            setTargetDataSources(dynamicTargetDataSources_temp);// 將map賦值給父類的TargetDataSources
            super.afterPropertiesSet();// 將TargetDataSources中的連接信息放入resolvedDataSources管理
            log.info(key+"數據源初始化成功");
            //log.info(key+"數據源的概況:"+druidDataSource.dump());
            return true;
        } catch (Exception e) {
            log.error(e + "");
            return false;
        }
    }
    // 刪除數據源
    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;
        }
    }

    // 測試數據源連接是否有效
    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("準備創建數據源"+datasourceId);
        Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        if (dynamicTargetDataSources2.containsKey(datasourceId)) {
            log.info("數據源"+datasourceId+"之前已經創建,準備測試數據源是否正常...");
            //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);
            DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId);
            boolean rightFlag = true;
            Connection connection = null;
            try {
//                log.info(datasourceId+"數據源的概況->當前閒置連接數:"+druidDataSource.getPoolingCount());
//                long activeCount = druidDataSource.getActiveCount();
//                log.info(datasourceId+"數據源的概況->當前活動連接數:"+activeCount);
//                if(activeCount > 0) {
//                    log.info(datasourceId+"數據源的概況->活躍連接堆棧信息:"+druidDataSource.getActiveConnectionStackTrace());
//                }
                log.info("準備獲取數據庫連接...");
                connection = druidDataSource.getConnection();
                log.info("數據源"+datasourceId+"正常");
            } catch (Exception e) {
                log.error(e.getMessage(),e); //把異常信息打印到日誌文件
                rightFlag = false;
                log.info("緩存數據源"+datasourceId+"已失效,準備刪除...");
                if(delDatasources(datasourceId)) {
                    log.info("緩存數據源刪除成功");
                } else {
                    log.info("緩存數據源刪除失敗");
                }
            } finally {
                if(null != connection) {
                    connection.close();
                }
            }
            if(rightFlag) {
                log.info("不需要重新創建數據源");
                return;
            } else {
                log.info("準備重新創建數據源...");
                createDataSource(dataSource);
                log.info("重新創建數據源完成");
            }
        } else {
            createDataSource(dataSource);
        }
        
    }
    
    private  void createDataSource(com.bitservice.adi.entity.DataSource dataSource) throws Exception {
        String datasourceId = dataSource.getDatasourceId();
        log.info("準備創建數據源"+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("數據源"+datasourceId+"配置正確,但是創建失敗",500);
            }
        } else {
            throw new ADIException("數據源配置有錯誤",500);
        }
    }

}

第4步:創建數據源配置類DruidDBConfig(類名可以改)
該類在springboot啓動時就會實例化,主要功能是創建主數據源對象和第3步的動態數據源對象。動態數據源對象手上有一個“袋子”,用來裝具體的數據源對象,通過代碼可以看到,我把主數據源對象也放到了這個“袋子”裏面。

代碼:

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數據庫連接信息
    @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對支持遊標的數據庫性能提升巨大,比如說oracle。在mysql下建議關閉。
        datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
        datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//對於耗時長的查詢sql,會受限於ReadTimeout的控制,單位毫秒
        datasource.setTestOnBorrow(true); //申請連接時執行validationQuery檢測連接是否有效,這裏建議配置爲TRUE,防止取到的連接不可用
        datasource.setTestWhileIdle(true);//建議配置爲true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
        String validationQuery = "select 1 from dual";
        datasource.setValidationQuery(validationQuery); //用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery爲null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
        datasource.setFilters("stat,wall");//屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有:監控統計用的filter:stat日誌用的filter:log4j防禦sql注入的filter:wall
        datasource.setTimeBetweenEvictionRunsMillis(60000); //配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
        datasource.setMinEvictableIdleTimeMillis(180000); //配置一個連接在池中最小生存的時間,單位是毫秒,這裏配置爲3分鐘180000
        datasource.setKeepAlive(true); //打開druid.keepAlive之後,當連接池空閒時,池中的minIdle數量以內的連接,空閒時間超過minEvictableIdleTimeMillis,則會執行keepAlive操作,即執行druid.validationQuery指定的查詢SQL,一般爲select * from dual,只要minEvictableIdleTimeMillis設置的小於防火牆切斷連接時間,就可以保證當連接空閒時自動做保活檢測,不會被防火牆切斷
        
        datasource.setRemoveAbandoned(true); //是否移除泄露的連接/超過時間限制是否回收。
        datasource.setRemoveAbandonedTimeout(3600); //泄露連接的定義時間(要超過最大事務的處理時間);單位爲秒。這裏配置爲1小時
        datasource.setLogAbandoned(true); ////移除泄露連接發生是是否記錄日誌
        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步:創建數據源切換類DBContextHolder(類名可以改)

代碼:

package com.bitservice.adi.datasource;

/**
 * 數據源切換
 *
 * @author aiyo92
 *
 */
public class DBContextHolder {
    // 對當前線程的操作-線程安全的
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

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

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

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

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

創建一個DAO類SqlRepository,專門用來操作各個客數據源。

以實現對客數據源的查詢請求爲例,假如平臺創建了多個數據源,併爲每個數據源定義了SQL語句,由於平臺底層執行SQL的方法是共用的(都在SqlRepository類裏面),那麼如何在執行查詢方法之前動態切換要查詢的客數據源呢?上代碼!

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("執行sql查詢doSelect-sql:" + sql);
        logger.info("sql_params:" + params);
        List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql, params);
        logger.info("查詢數據庫結果doSelect-result:" + resultList.toString());
        return resultList;
    }
}

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

最後溫馨提示:以上我給出的只是核心代碼,大家直接粘貼使用的話肯定會報錯(比如檢查字符串是否爲空的工具類不是官方提供的,大家可以使用官方提供的類),報錯的地方大家一看就知道怎麼改,所以你們只需要把報錯的代碼根據實際情況換成自己的代碼即可。如果有任何疑問,歡迎給我留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章