Spring多數據源配置

Spring多數據源配置原理

Spring的多數據源配置主要靠是AbstractRoutingDataSource類,該類中有個抽象方法用來獲取數據源的名稱,建立一個Java類來實現獲取數據源名稱的方法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();

複製代碼

存在多線程的安全性問題,將數據源名稱變量存到本地線程裏面,獲取數據源名稱時能夠直接從本地線程中獲取spring

/**
 * 保存數據源名稱的類
 */

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動態數據庫切換原理apache

  • 查詢數據庫記錄時手動切換數據源
<!--手動切換數據源-->
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中獲取鏈接時會直接從事務管理器裏面獲取,這樣就會致使數據源切換失敗安全

相關文章
相關標籤/搜索