1. 程式人生 > >spring多資料來源配置

spring多資料來源配置

一、問題提出

專案中我們經常會遇到多資料來源的問題,尤其是資料同步或定時任務等專案更是如此。多資料來源讓人最頭痛的,不是配置多個數據源,而是如何能靈活動態的切換資料來源。例如在一個spring和hibernate的框架的專案中,我們在spring配置中往往是配置一個dataSource來連線資料庫,然後繫結給sessionFactory,在dao層程式碼中再指定sessionFactory來進行資料庫操作。

二、程式碼實現

1. 首先在配置檔案中配置多個dataSource

	<bean id="dataSourceMySql" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
		<property name="driverClass" value="${mysql.jdbc.driverClass}" />
		<property name="jdbcUrl" value="${mysql.jdbc.url}" />
		<property name="user" value="${mysql.jdbc.user}" />
		<property name="password" value="${mysql.jdbc.password}" />
		<property name="initialPoolSize" value="${mysql.jdbc.initialPoolSize}" />
		<property name="minPoolSize" value="${mysql.jdbc.minPoolSize}" />
		<property name="maxPoolSize" value="${mysql.jdbc.maxPoolSize}" />
		<property name="checkoutTimeout" value="${mysql.jdbc.checkoutTimeout}" />
		<property name="idleConnectionTestPeriod" value="${mysql.jdbc.idleConnectionTestPeriod}" />
		<property name="maxIdleTime" value="${mysql.jdbc.maxIdleTime}" />
		<property name="maxStatements" value="${mysql.jdbc.maxStatements}" />
		<property name="testConnectionOnCheckout" value="${mysql.jdbc.testConnectionOnCheckout}" />
	</bean>

	<bean id="dataSourceOracle" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
		<property name="driverClass" value="${oracle.jdbc.driverClass}" />
		<property name="jdbcUrl" value="${oracle.jdbc.url}" />
		<property name="user" value="${oracle.jdbc.user}" />
		<property name="password" value="${oracle.jdbc.password}" />
		<property name="initialPoolSize" value="${oracle.jdbc.initialPoolSize}" />
		<property name="minPoolSize" value="${oracle.jdbc.minPoolSize}" />
		<property name="maxPoolSize" value="${oracle.jdbc.maxPoolSize}" />
		<property name="checkoutTimeout" value="${oracle.jdbc.checkoutTimeout}" />
		<property name="idleConnectionTestPeriod" value="${oracle.jdbc.idleConnectionTestPeriod}" />
		<property name="maxIdleTime" value="${oracle.jdbc.maxIdleTime}" />
		<property name="maxStatements" value="${oracle.jdbc.maxStatements}" />
		<property name="testConnectionOnCheckout" value="${oracle.jdbc.testConnectionOnCheckout}" />
	</bean>

2. 擴充套件Spring的AbstractRoutingDataSource抽象類,實現動態資料來源。
AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是實現資料來源的route的核心.這裡對該方法進行Override。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		return DatabaseContextHolder.getCustomerType();
	}

}

上下文DatabaseContextHolder為一執行緒安全的ThreadLocal,具體程式碼如下:

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();
	}
}

3. 配置動態資料來源
將DynamicDataSource Bean加入到Spring的上下文xml配置檔案中去,同時配置DynamicDataSource的targetDataSources(多資料來源目標)屬性的Map對映。
	<bean id="dataSource" class="com.core.DynamicDataSource">
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<entry key="dataSourceMySql" value-ref="dataSourceMySql" />
				<entry key="dataSourceOracle" value-ref="dataSourceOracle" />
			</map>
		</property>
		<property name="defaultTargetDataSource" ref="dataSourceMySql" />
	</bean>

4. 使用動態資料來源
DynamicDataSource是繼承與AbstractRoutingDataSource,而AbstractRoutingDataSource又是繼承於org.springframework.jdbc.datasource.AbstractDataSource,AbstractDataSource實現了統一的DataSource介面,所以DynamicDataSource同樣可以當一個DataSource使用。
<!-- JdbcTemplate使用動態資料來源的配置 -->     
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">     
    <property name="dataSource">     
        <ref bean="dataSource" />     
    </property>     
</bean>     
     
<!-- 對JdbcTemplate的應用封裝類 -->     
<bean id="sqlBaseDAO" class="com.whty.dao.BaseDAOImpl">     
    <property name="jdbcTemplate">     
        <ref bean="jdbcTemplate" />     
    </property>     
</bean>  
5. 動態資料來源的管理
如何選擇控制每個業務中需要的具體資料來源,可是使用手動控制:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BaseDAO dao = (BaseDAO) context.getBean("baseDAO", BaseDAOImpl.class);

try {
    DatabaseContextHolder.setCustomerType("dataSourceMySql");
    System.err.println(dao.select("select count(*) sum from TEST t ").get(0).get("SUM"));
    DatabaseContextHolder.setCustomerType("dataSourceOracle");
    System.err.println(dao.select("select count(*) sum from TEST t ").get(0).get("SUM"));
} catch (Exception e) {
    e.printStackTrace();
}
如果在service層有比較統一的規則的話,也可以使用aop設定資料來源使用。
	<bean id="dataSourceInterceptor" class="com.core.DataSourceInterceptor" />

	<aop:config>
		<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
			<aop:pointcut id="dsMysql" expression="execution(* com.controller.mysql.*.*(..))" />
			<aop:pointcut id="dsOracle" expression="execution(* com.controller.oracle.*.*(..))" />
			<aop:before method="setdataSourceMysql" pointcut-ref="dsMysql"/>
			<aop:before method="setdataSourceOracle" pointcut-ref="dsOracle"/>
		</aop:aspect>
	</aop:config>
import org.aspectj.lang.JoinPoint;

public class DataSourceInterceptor {

	public void setdataSourceMysql(JoinPoint jp) {
		DatabaseContextHolder.setCustomerType("dataSourceMySql");
	}
	
	public void setdataSourceOracle(JoinPoint jp) {
		DatabaseContextHolder.setCustomerType("dataSourceOracle");
	}
}



這裡一般都是一個service一個數據源,所以最好使用aop在service層執行完之後統一呼叫
DBContextHolder.clearDBType();  
清空資料來源資訊。

三、注意事項

1. 注意事務攔截器的配置
這是首要的一條。首先你要明白,Spring的事務管理是與資料來源繫結的,一旦程式執行到事務管理的那一層(如service)的話,由於在進入該層之前事務已經通過攔截器開啟,因此在該層切換資料來源是不行的,明白事務的原理是尤為重要的,我之前的文章中,將切換資料來源的攔截器配置在了Dao層是有問題的(因為是示例,所以粗心了,對誤導了大家我表示道歉),但提供的思路是沒有問題的。
Demo中將切換資料來源的攔截器(dataSourceInterceptor)配置在了事務攔截器(txadvice)的上一層,也就是Controller層。
2. 注意資料庫表的建立
一些人喜歡用Hibernate的自動建立表的功能,但需要注意,在多資料來源中,尤其是不同資料庫的多資料來源,想都自動建表是不行的。因為Hibernate自動建表是在專案啟動時觸發的,因此只會建立專案配置的預設資料來源的表,而其他資料來源的表則不會自動建立。大家要注意著點。
3. Hibernate的資料庫方言(dialect)可以忽略
在多資料來源時,方言的設定可以忽略,Hibernate在使用時會自動識別不同的資料庫,因此不必糾結這個配置,甚至不配置也可以。
4. 報No current session錯誤
這個是因為使用了sessionFactory.getCurrentSession()導致的,current session是與執行緒繫結的,一個執行緒只會開啟一個Session(除非使用openSession()就不會報錯),因此需要設定session與執行緒的繫結關係。
Demo中使用了Spring管理Hibernate的session,因此在web.xml中配置了OpenSessionInViewFilter,並在hibernate.cfg.xml中配置了current_session_context_class。【PS:使用Spring管理Hibernate時,可以去掉hibernate.cfg.xml,而全部配置的Spring的配置檔案裡,即hibernateProperties。看個人喜好吧】