Spring多資料來源配置原理
Spring的多資料來源配置主要靠是AbstractRoutingDataSource類,該類中有個抽象方法用來獲取資料來源的名稱,建立一個Java類來實現獲取資料來源名稱的方法
- AbstractRoutingDataSource獲取資料來源名稱的方法
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
//獲取資料來源名稱
Object lookupKey = determineCurrentLookupKey();
//通過相應的資料來源名稱來獲取相應的資料來源
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
protected abstract Object determineCurrentLookupKey();
複製程式碼
存在多執行緒的安全性問題,將資料來源名稱變數存到本地執行緒裡面,獲取資料來源名稱時可以直接從本地執行緒中獲取
/**
* 儲存資料來源名稱的類
*/
public class DatabaseContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
/**
* 獲取資料來源名稱的類
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getCustomerType();
}
}
複製程式碼
切換資料來源的時候,可以選擇配置AOP去切換資料來源,也可以手動通DatabaseContextHolder.setCustomerType(dataSourceName)去切換資料來源
- xml配置檔案
<!--資料來源1-->
<bean id="dataSourcev14" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.v14.driverClassName}"/>
<property name="url" value="${jdbc.v14.url}"/>
<property name="username" value="${jdbc.v14.username}"/>
<property name="password" value="${jdbc.v14.password}"/>
</bean>
<!--資料來源2-->
<bean id="dataSourcev11" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.v11.driverClassName}"/>
<property name="url" value="${jdbc.v11.url}"/>
<property name="username" value="${jdbc.v11.username}"/>
<property name="password" value="${jdbc.v11.password}"/>
</bean>
<!--動態資料來源引用-->
<bean id="dynamicDataSource" class="cn.com.egova.source.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSourcev11" value-ref="dataSourcev11"></entry>
<entry key="dataSourcev14" value-ref="dataSourcev14"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourcev14">
</property>
</bean>
<!--Spring Jdbc引用-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dynamicDataSource"/>
</bean>
複製程式碼
Spring動態資料庫切換原理
- 查詢資料庫記錄時手動切換資料來源
<!--手動切換資料來源-->
DatabaseContextHolder.setCustomerType("dataSourcev11");
<!--查詢相應的資料庫記錄-->
int count2 = jdbcTemplate.queryForObject("select count(*) from to_stat_info",Integer.class);
複製程式碼
- jdbcTemplate類查詢原始碼
<!--經過層層的封裝之後到了execute方法-->
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
<!--獲取資料來源連線方法-->
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
T result = action.doInStatement(stmtToUse);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
複製程式碼
- DataSourceUtils類中getConnection方法
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//首先檢視事務管理器裡面是否有Connection連線
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
logger.debug("Fetching JDBC Connection from DataSource");
//從資料來源中獲取連線,實現AbstractRoutingDataSource類中方法(因為配置檔案中dataSource實現類是DynamicDataSource繼承了AbstractRoutingDataSource抽象類)
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}
複製程式碼
- AbstractRoutingDataSource類中獲取資料來源Connection連線方法
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
//determineTargetDataSource方法
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
//剛才建立的DynamicDataSource,實現AbstractRoutingDataSource抽象方法獲取資料來源名稱
Object lookupKey = determineCurrentLookupKey();
//通過資料來源名稱來獲取相應的資料來源
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
//getConnection方法
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
複製程式碼
注意
實現動態資料來源時,要注意一下aop的事務配置,因為aop的管理器會在方法執前先注入資料來源,後面doGetConnection中獲取連線時會直接從事務管理器裡面獲取,這樣就會導致資料來源切換失敗