1. 程式人生 > >spring動態建立,載入,使用多資料來源

spring動態建立,載入,使用多資料來源

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

正如上圖所示,每一塊都是指定綁死的,如果是多個數據源,也只能是下圖中那種方式。

可看出在Dao層程式碼中寫死了兩個SessionFactory,這樣日後如果再多一個數據源,還要改程式碼新增一個SessionFactory,顯然這並不符合開閉原則。

那麼正確的做法應該是

先說一下我的思路:

首先做一個建立資料來源配置檔案的類,專門用於建立資料來源xml配置,叫ConfigFileCreator.java 

其次,編寫一個動態載入剛才建立的配置檔案的類,叫 DynamicLoadBean.java。至此,建立的資料來源到記憶體中已經完成。

第三,重點編寫這個DynamicDataSource.java,該類主要是用來實現動態切換資料來源,並通知容器。在這一塊我選擇了修改,AbstractRoutingDataSource.java,編寫一個支援新增資料來源物件的方法public void addDataSource(Object key,Object dataSource)

第四,寫例子來測試

實現過程如下:

1.bean.xml配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
				http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
	<!-- 自動掃描與裝配bean -->
	<context:component-scan base-package="qilin"></context:component-scan>
	<!-- 使用外部的配置檔案 -->
	<context:property-placeholder location="classpath:jdbc.properties" />
	<bean id="defaultDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
				<!-- 資料庫連線資訊 -->
				<property name="jdbcUrl" value="${jdbcUrl}"></property>
				<property name="driverClass" value="${driverClass}"></property>
				<property name="user" value="${username}"></property>
				<property name="password" value="${password}"></property>
	</bean>
	
	<bean id="dataSourceA" class="com.mchange.v2.c3p0.ComboPooledDataSource">
				<!-- 資料庫連線資訊 -->
				<property name="jdbcUrl" value="jdbc:mysql:///dynamictest"></property>
				<property name="driverClass" value="${driverClass}"></property>
				<property name="user" value="${username}"></property>
				<property name="password" value="${password}"></property>
	</bean>
	<bean id="dynamicdatasource" class="qilin.utils.DynamicDataSource">
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				 <entry key="defaultDataSource" value-ref="defaultDataSource" /> 
				 <entry key="dataSourceA" value-ref="dataSourceA" /> 
			</map>
		</property>
		<property name="defaultTargetDataSource" ref="defaultDataSource" />
	</bean>

	<!-- 配置SessionFactory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dynamicdatasource" />
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
				<prop key="hibernate.hbm2ddl.auto">none</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">false</prop>
				<prop key="hibernate.temp.use_jdbc_metadata_defaults">false</prop>
			</props>
		</property>
		<!-- 註解方式配置 -->
		<property name="annotatedClasses">
			<list>
				<value>qilin.entity.Student</value>
			</list>
		</property>
	</bean>


	<!-- 配置宣告式事務管理,採用基於註解的方式 -->
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory"></property>
	</bean>
	<tx:annotation-driven transaction-manager="transactionManager" />
	<bean id="dynamicLoadBean" class="qilin.utils.DynamicLoadBean"></bean>
</beans>
2.AbstractRoutingDataSource.class 修改的部分
/**@author qilin**/
	public void addDataSource(Object key,Object dataSource){
		
		this.targetDataSources.put(key, dataSource);
		
		setTargetDataSources(this.targetDataSources);
	}
3.DynamicDataSource.class  這個類中有一個地方需要注意,當我們新增資料,切換了資料來源,要通知當前spring容器,需要呼叫父類的super.afterPropertiesSet();方法
package qilin.utils;

import java.util.Map;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;

public class DynamicDataSource extends AbstractRoutingDataSource {

	/* 
	 * 該方法必須要重寫  方法是為了根據資料庫標示符取得當前的資料庫
	 */
	@Override
	public Object determineCurrentLookupKey() {
		return DataSourceContextHolder.getDataSourceName();
	}

	@Override
	public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
		super.setDataSourceLookup(dataSourceLookup);
	}

	@Override
	public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
		super.setDefaultTargetDataSource(defaultTargetDataSource);
	}

	@Override
	public void setTargetDataSources(Map targetDataSources) {
		super.setTargetDataSources(targetDataSources);
		//重點
		super.afterPropertiesSet();
	}

}
4.動態載入資料來源配置檔案DynamicLoadBean.class
package qilin.utils;

import java.io.IOException;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ChildBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.ResourceEntityResolver;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * 動�?�載入資料來源
 * @author qilin
 *
 */
public class DynamicLoadBean implements ApplicationContextAware {

	private ConfigurableApplicationContext applicationContext = null;
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {

			this.applicationContext = (ConfigurableApplicationContext) applicationContext;
	}

	public ConfigurableApplicationContext getApplicationContext() {  
        return applicationContext;  
    }
	
	/**
	 * 1.配置檔案的位置固�?
	 * 2.配置檔案中bean的名字已�?
	 * @param configLocationString
	 */
	 public void loadBean(String fileName)
	    {  
	        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry)getApplicationContext().getBeanFactory());  
	        beanDefinitionReader.setResourceLoader(getApplicationContext());  
	        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(getApplicationContext()));  
	        try
	        {  
	        	beanDefinitionReader.loadBeanDefinitions(getApplicationContext().getResources(fileName));  
	        } catch (BeansException e) {  
	            e.printStackTrace();  
	        } catch (IOException e) {  
	            e.printStackTrace();  
	        }  
	    }  

	 public void registBean(String beanName, String parentName) {
		 DefaultListableBeanFactory  fcy = (DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
		 BeanDefinition beanDefinition  = new ChildBeanDefinition(parentName);
		 fcy.registerBeanDefinition(beanName, beanDefinition);
	 }
}
5.DataSourceContextHolder.class
package qilin.utils;


public class DataSourceContextHolder {

	private static final ThreadLocal contextHolder=new ThreadLocal();
	
	public static void setDataSourceType(String dataSourceName){
		contextHolder.set(dataSourceName);
	}
	
	public static String getDataSourceName(){
		return (String) contextHolder.get();
	}
	
	public static void clearDataSourceType(){
		contextHolder.remove();
	}
	
}

5.測試程式碼

package junit;

import java.util.HashMap;
import java.util.Map;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import qilin.entity.Student;
import qilin.service.StuService;
import qilin.utils.DataSourceContextHolder;
import qilin.utils.DynamicDataSource;
import qilin.utils.DynamicLoadBean;

public class Test {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		
		StuService stuService = (StuService) ac.getBean("stuServiceImpl");
		
		Student stu = new Student();
		
		stu.setStuName("qilin");
		
		stuService.save(stu);
		
		DataSourceContextHolder.setDataSourceType("dataSourceA");
		
		
		Student _stu = new Student();
		
		_stu.setStuName("_qilin1");
		
		stuService.save(_stu);
		
		//建立一個數據源
		DynamicLoadBean dynamicBeanLoad =(DynamicLoadBean)ac.getBean("dynamicLoadBean");   
        dynamicBeanLoad.loadBean("classpath:mos/qqq.xml"); 
        ComboPooledDataSource dataSource = (ComboPooledDataSource) ac.getBean("qqq");
        System.err.println(dataSource.getDriverClass());
        
        
        
        DynamicDataSource dynamicDataSource = (DynamicDataSource) ac.getBean("dynamicdatasource");
       
//        Map<String, ComboPooledDataSource> targetDataSources = new HashMap<String, ComboPooledDataSource>();
        
//        targetDataSources.put("qqq", dataSource);
        
//        Map<Object, Object> targetDataSources = dynamicDataSource.getTargetDataSources();
        
//        targetDataSources.put("qqq", dataSource);
        
//        dynamicDataSource.setTargetDataSources(targetDataSources);
        
//        dynamicDataSource.setDefaultTargetDataSource(dataSource);
        
        dynamicDataSource.addDataSource("qqq", dataSource);
        
        DataSourceContextHolder.setDataSourceType("qqq");
		
        Student __stu = new Student();
		
		__stu.setStuName("qilin2");
		
		stuService.save(__stu);
		
	}
}


切換資料來源就一句話搞定  DataSourceContextHolder.setDataSourceType("qqq");