spring 動態數據源配置以及相關問題

項目中要求讀寫分離,在spring中做到讀寫分離,很簡單的想到在配置文件中設置兩個數據源,一個datesource(只寫),一個datesourceread(只讀)。但是要根據上下文動態切換數據源,還需要增加兩個幫助類。

      類1 ContextHolder

主要功能是幫助切換數據源,其中ThreadLocal保證線程中的一致性,不受其他線程影響。

public class ContextHolder {
	public static final String DATA_SOURCE = "dataSource";
	public static final String DATA_SOURCE_READ = "dataSourceRead";
	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();
	}
}

   類2 MyDataSource

自定義數據源,繼承AbstractRoutingDataSource,實現determineCurrentLookupKey()方法。

public class MyDataSource extends AbstractRoutingDataSource {

	/* (non-Javadoc)
	 * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		return ContextHolder.getCustomerType();
	}

}

配置文件中這樣配置:

<bean id="abstractDataSource" abstract="true"  
                class="com.mchange.v2.c3p0.ComboPooledDataSource"  
                destroy-method="close">
        <property name="driverClass"
			value="#{configManager.getConfigValue('trade-seller-guestbook-serv','datasource.driverClassName')}" />
        <property name="acquireIncrement" value="3" />
        <property name="initialPoolSize" value="3" />
        <property name="minPoolSize" value="2" />
        <property name="maxPoolSize" value="50" />
        <property name="maxIdleTime" value="600" />
        <property name="idleConnectionTestPeriod" value="900" />
        <property name="maxStatements" value="100" />
        <property name="numHelperThreads" value="10" />
	</bean>
	
	<bean id="dataSourceRead" parent="abstractDataSource">
        <property name="jdbcUrl"
                  value="jdbc:mysql://192.168.10.57:3306/trade?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull" />
        <property name="user" value="root" />
        <property name="password" value="canada" />
        <!-- <property name="jdbcUrl"
			value="#{configManager.getMysqlConfig('trade_public_mysql','1.2','trade').url}" />
		<property name="user"
			value="#{configManager.getMysqlConfig('trade_public_mysql','1.2','trade').user}" />
		<property name="password"
			value="#{configManager.getMysqlConfig('trade_public_mysql','1.2','trade').pwd}" /> -->
    </bean>
	
	<bean id="dataSource" parent="abstractDataSource">
		<property name="jdbcUrl"
			value="#{configManager.getMysqlConfig('trade_public_mysql','1.1','trade').url}" />
		<property name="user"
			value="#{configManager.getMysqlConfig('trade_public_mysql','1.1','trade').user}" />
		<property name="password"
			value="#{configManager.getMysqlConfig('trade_public_mysql','1.1','trade').pwd}" />
	</bean>
	
	<bean id="myDataSource" class="com.zhe800.guestbook.database.MyDataSource">
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<entry value-ref="dataSource" key="dataSource"/>
				<entry value-ref="dataSourceRead" key="dataSourceRead"/>
			</map>
		</property>
		<property name="defaultTargetDataSource" ref="dataSource"/>
	</bean>
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="myDataSource" />
		<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
		<property name="mapperLocations">
			<array>
				<value>classpath*:mybatis/sqlmap/guestbook/*.xml</value>
				<value>classpath*:mybatis/sqlmap/manual/*.xml</value>
			</array>
		</property>
	</bean>

	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.zhe800.guestbook.model.mapper" />
		<property name="sqlSessionFactory" ref="sqlSessionFactory" />
	</bean>

	<tx:annotation-driven transaction-manager="transactionManager" />
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="myDataSource" />
	</bean>


這樣在做數據庫操作的時候,讀的時候只需要設置,寫的時候還是默認數據源就ok了。

 ContextHolder.setCustomerType(ContextHolder.DATA_SOURCE_READ);


1、一直有一個疑問,既然xml配置中bean是啓動的時候就加載到內存當中,且全局只有一個對象,那麼在運行時設置ContextHolder參數可以修改數據源呢?

2、爲什麼在配置了事務的時候(@transaction),是改變不了數據源的呢?


對於第一個問題,在設置了ContextHolder之後,再開始使用sqlSessionFactory進行數據庫相關操作,這時候sqlSessionFactory會找到真正的datasource

public void setDataSource(DataSource dataSource) {
        if (dataSource instanceof TransactionAwareDataSourceProxy) {
            // If we got a TransactionAwareDataSourceProxy, we need to perform
            // transactions for its underlying target DataSource, else data
            // access code won't see properly exposed transactions (i.e.
            // transactions for the target DataSource).
            this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
        } else {
            this.dataSource = dataSource;
        }
    }
而mydatesource繼承自AbstractRoutingDataSource,類中精華部分在這裏

/**
	 * Retrieve the current target DataSource. Determines the
	 * {@link #determineCurrentLookupKey() current lookup key}, performs
	 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
	 * falls back to the specified
	 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
	 * @see #determineCurrentLookupKey()
	 */
	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;
	}

他會調用determineCurrentLookupKey()方法選擇合適的數據源的key,而這個方法在我們的MyDataSource類中已經實現,且將向下文需要的ContextHolder設置了進去。

這樣就實現了運行時的動態數據庫的選擇。



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