轉載網友的,文章附原文鏈接,個人找了很久沒有找到好的方法解決,
配置多個數(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.username}")
private String username;
@Value("{spring.datasource.driverClassName}")
private String driverClassName;// 連接池連接信息
@Value("{spring.datasource.minIdle}")
private int minIdle;
@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