欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring 多数据库支持,动态切换数据库

程序员文章站 2022-07-14 13:58:03
...

为了备份特将此新的发到博客当中以备查找。

数据库的动态切换在很多项目当中都有应用,经我查阅了多篇文档,整合思路最终成功实现数据源的动态切换功能,并稳定运行了一段时间未发现异常。

 

我的数据源切换时根据域名并配合spring来切换的,不同的域名访问不同的数据源,当然可以根据其他的需求进行动态切换。

 

首先需要配置一个过滤器来过滤域名,并动态切换数据源。

web.xml 增加如下代码:

     <!-- 根据域名动态切换数据源 -->
     <filter>
    	<filter-name>ServerFilter</filter-name>
    	<filter-class>com.wfy.filter.ServerFilter</filter-class>
    </filter>
    <filter-mapping>
    	<filter-name>ServerFilter</filter-name>
    	<url-pattern>/*</url-pattern>
    </filter-mapping>

 

对应写入相应的java类:

package com.wfy.filter;

import java.io.File;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import com.wfy.multiData.SpObserver;

public class ServerFilter extends HttpServlet implements Filter {

	private static final long serialVersionUID = 6452049924844786456L;
	private static FilterConfig filterConfig;
	public void destroy() {
		// TODO Auto-generated method stub

	}
	public void init(FilterConfig filterConfig) throws ServletException
	{
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain filterChain) throws IOException, ServletException {
		/**
		 * 需要在此处操作数据源,将数据源切换到新定义好的数据源当中。
		 */
		
		System.out.println("request.getServerName():_"+request.getServerName());
		String url = Thread.currentThread().getContextClassLoader().getResource("") + "dataSource/_"+request.getServerName()+".xml";
		File f=new File(url.substring(6, url.length()));
		//确认数据源配置文件存在,切换数据源
		if(f.exists()){
			SpObserver.putSp("_"+request.getServerName());
		}else{
		//如果数据源配置文件不存在,使用默认数据源
			SpObserver.putSp("_dataSource");
		}
		
		filterChain.doFilter(request, response);
	}
}

 

SpObserver类:

package com.wfy.multiData;

/**
 * @author 金鑫
 *
 */
public class SpObserver {
	private static ThreadLocal local = new ThreadLocal();

	public static void putSp(String sp) {
		local.set(sp);
	}

	public static String getSp() {
		return (String)local.get();
	}
}

  这样过滤器的设置就算完成了,下面是spring的设置

spring配置文件:

	<!-- 数据库模块 初始化 -->
	<bean id="DynamicLoadBean" class="com.wfy.multiData.DynamicLoadBean" />
	<!--可以通过他动态增加新的数据源-->
	<bean id="InitDataSource" class="com.wfy.init.InitDataSource"></bean>

	<!--配置统一数据源,将来系统中使用的数据源将从他当中获取-->
	<bean id="DataSource" class="com.wfy.multiData.MultiDataSource">
		<property name="dataSource">  
			<ref bean="_dataSource" />  
		</property>  
	</bean>

 

MultiDataSource类:

package com.wfy.multiData;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.wfy.exceptionAdvisor.BusinessException;

/**
 * @author 金鑫
 *
 */
public class MultiDataSource implements DataSource,ApplicationContextAware {

	private static final Log log = LogFactory.getLog(MultiDataSource.class);
	private ApplicationContext applicationContext = null;
	private DataSource dataSource = null;
	/* (non-Javadoc)
	 * @see javax.sql.DataSource#getConnection()
	 */
	public Connection getConnection() throws SQLException {
		return getDataSource().getConnection();
	}

	/* (non-Javadoc)
	 * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
	 */
	public Connection getConnection(String arg0, String arg1)
			throws SQLException {
		return getDataSource().getConnection(arg0, arg1);
	}

	/* (non-Javadoc)
	 * @see javax.sql.DataSource#getLogWriter()
	 */
	public PrintWriter getLogWriter() throws SQLException {
		return getDataSource().getLogWriter();
	}

	/* (non-Javadoc)
	 * @see javax.sql.DataSource#getLoginTimeout()
	 */
	public int getLoginTimeout() throws SQLException {
		return getDataSource().getLoginTimeout();
	}

	/* (non-Javadoc)
	 * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
	 */
	public void setLogWriter(PrintWriter arg0) throws SQLException {
		getDataSource().setLogWriter(arg0);
	}

	/* (non-Javadoc)
	 * @see javax.sql.DataSource#setLoginTimeout(int)
	 */
	public void setLoginTimeout(int arg0) throws SQLException {
		getDataSource().setLoginTimeout(arg0);
	}

	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
	
	public DataSource getDataSource(String dataSourceName){
		log.debug("dataSourceName:"+dataSourceName);
		try{
			if(dataSourceName==null||dataSourceName.equals("")){
				return this.dataSource;
			}
			return (DataSource)this.applicationContext.getBean(dataSourceName);
		}catch(NoSuchBeanDefinitionException ex){
			throw new BusinessException("没有 <name:"+dataSourceName+"> 数据源 在系统当中!");
		}
	}
	
	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public DataSource getDataSource(){
		String sp = SpObserver.getSp();
		return getDataSource(sp);
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}
}

 

DynamicLoadBean类:

package com.wfy.multiData;

import java.io.IOException;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
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.support.AbstractXmlApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;

/**
 * 使用方法loadBean()向spring的beanFactory动态地装载bean,该方法的参数configLocationString等同于
 * spring配置中的contextConfigLocation,同样支持诸如"/WEB-INF/ApplicationContext-*.xml"的写法。
 * @author FanGang
 *
 */
public class DynamicLoadBean implements ApplicationContextAware{

	private XmlWebApplicationContext applicationContext = null;
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = (XmlWebApplicationContext)applicationContext;
	}
	public XmlWebApplicationContext getApplicationContext() {
		return applicationContext;
	}
	
	/**
	 * 向spring的beanFactory动态地装载bean
	 * @param configLocationString 要装载的bean所在的xml配置文件位置。
	 */
	public void loadBean(String configLocationString){
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry)getApplicationContext().getBeanFactory());
		beanDefinitionReader.setResourceLoader(getApplicationContext());
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(getApplicationContext()));
		try {
			String[] configLocations = new String[]{configLocationString};
			for(int i=0;i<configLocations.length;i++){
				System.out.println(configLocations[i]);
				beanDefinitionReader.loadBeanDefinitions(getApplicationContext().getResources(configLocations[i]));
			}	
		} catch (BeansException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

InitDataSource类(如果在系统运行的时候需要动态增加数据源的话可以调用此类来执行):

package com.wfy.init;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;


import com.wfy.multiData.DynamicLoadBean;

public class InitDataSource implements ApplicationContextAware {
	private ApplicationContext applicationContext = null;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;
	}

	public void init(String url){
		DynamicLoadBean dynamicLoadBean = (DynamicLoadBean)this.applicationContext.getBean("DynamicLoadBean");
		dynamicLoadBean.loadBean("classpath:dataSource/"+ url +".xml");
	}
}

 这样基本数据源配置就算完成了,在系统运行当时就可以根据不同的域名(连接地址)来动态的切换数据库了。

 

另外我的数据源配置文件是单独保存在一个文件夹当中的,这样服务在启动的时候就会动态的去加载这些数据源了

比如:

我的数据源文件是存放处classpath:dataSource文件夹里面,

那我的文件夹下面就会有很多的类似文件名的xml文件:_192.168.234.250.xml,_dataSource.xml(必须配置,默认数据源文件),_wfyerp.gnway.net.xml,然后在系统启动的时候就会去动态加载这些文件了。

如:当我们访问的地址是:http://wfyerp.gnway.net 这个网址的时候,实际数据库访问的将会是_wfyerp.gnway.net.xml文件中的数据源。

现在贴上这些配置文件的内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="_wfyerp.gnway.net" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"></property>
		<property name="url" value="jdbc:sqlserver://192.168.234.234:1433;databaseName=fy_erp_v6"></property>
		<property name="username" value="sa"></property>
		<property name="password" value="wfyerp2005"></property>
	</bean>
</beans>

 

然后会了能让spring在启动的时候加载这些配置文件,我们需要修改web.xml文件来让他启动的时候加载他们

web.xml修改spring配置如下:

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml, (classpath:dataSource/*.xml 需要新增的配置信息)</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

 

文件内打括号的部分为新增的配置信息,其他跟默认不改变即可。

 

到此我们通过spring+过滤器 来动态切换数据源的问题就得到圆满解决了。

 

另附样例一个,导入spring2.5的包,然后index.jsp是演示不同数据源的效果,大家稍加改用几个,需要修改的地方有:

数据库连接,已经域名地址,如我的ip地址为:192.168.234.250 所以我的数据源配置文件就包含了这个文件,然后包含了localhost文件,这样就能看出数据源切换的效果来了。