目前项目上使用spring的多数据源的架构,这里我简要的描述一下:目前系统需要在多个省下的多个地市使用,一方面为了避免系统升级一次每个地市都要升级,另一方面为了实现每个省的业务数据存放到自己的数据库里。所以将所有的用户信息和数据源的信息,以及他们之间的映射关系都存放到中心端的基础数据库。用户首先登陆的时候首先连接的是中心端的基础数据库,根据用户信息获取对应的数据源信息,重新建立连接,就可以实现数据源的动态切换了,值得注意的是用户登录后的数据源信息存放在浏览器端的Cookie中,这样能保证每次进行的数据库访问中,数据源信息是使用切换后的,而不是配置文件中的默认的数据源。


一、数据源信息表建表语句:


create table SYSDATASOURCE
(
  id           NUMBER not null,
  driver       VARCHAR2(50),
  url          VARCHAR2(50),
  ds_username  VARCHAR2(50),
  ds_password  VARCHAR2(50),
  yxbz         VARCHAR2(1) default '1' not null,
  sortnum      NUMBER default 0 not null,
  ip           VARCHAR2(30),
  portname     VARCHAR2(8),
  instancename VARCHAR2(50),
  maxactive    VARCHAR2(20),
  maxwait      VARCHAR2(10)
)
tablespace AA
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    next 8K
    minextents 1
    maxextents unlimited
  );
-- Add comments to the columns 
comment on column SYSDATASOURCE.id
  is '主键';
comment on column SYSDATASOURCE.driver
  is '数据库驱动';
comment on column SYSDATASOURCE.url
  is '数据库连接串';
comment on column SYSDATASOURCE.ds_username
  is '数据库用户名';
comment on column SYSDATASOURCE.ds_password
  is '数据库密码';
comment on column SYSDATASOURCE.yxbz
  is '有效标志';
comment on column SYSDATASOURCE.sortnum
  is '排序';
comment on column SYSDATASOURCE.ip
  is '数据库服务器的IP地址';
comment on column SYSDATASOURCE.portname
  is '端口号(例如:1521)';
comment on column SYSDATASOURCE.instancename
  is '实例名(例如:orcl)';
comment on column SYSDATASOURCE.maxactive
  is '最大连接数';
comment on column SYSDATASOURCE.maxwait
  is '最大等待毫秒数, 单位为 ms, 超过时间会出错误信息';
-- Create/Recreate primary, unique and foreign key constraints 
alter table SYSDATASOURCE
  add constraint PK_SYSDATASOURCE primary key (ID)
  using index 
  tablespace AA
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );
备注:用户和数据源的映射信息就存放在用户信息表中(ID,USERID,SYSDATASOURCEID);

二、实体类


package com.winning.bi.domain.sys;

import java.io.Serializable;

/**   
 * @标题: SysDataSource.java 
 * @包名: com.winning.bi.domain.sys 
 * @描述: 数据源实体类
 * @作者: yanghao
 * @时间: Oct 28, 2015 12:46:12 PM 
*/
public class SysDataSource implements Serializable
{

	/**
	 * @Fields serialVersionUID : TODO
	 */
	private static final long serialVersionUID = 1L;

	private long id;
	private String driver;
	private String url;
	private String dsUsername;
	private String dsPassword;
	private String yxbz;
	private long sortNum;
	private String ip;
	private String portName;
	private long maxWait;//最大等待时间
	private int maxActive;//最大连接数
	public String getPortName() {
		return portName;
	}

	public void setPortName(String portName) {
		this.portName = portName;
	}

	private String instanceName;

	public long getId()
	{
		return id;
	}

	public void setId(long id)
	{
		this.id = id;
	}

	public String getDriver()
	{
		return driver;
	}

	public void setDriver(String driver)
	{
		this.driver = driver;
	}

	public String getUrl()
	{
		return url;
	}

	public void setUrl(String url)
	{
		this.url = url;
	}

	public String getDsUsername()
	{
		return dsUsername;
	}

	public void setDsUsername(String dsUsername)
	{
		this.dsUsername = dsUsername;
	}

	public String getDsPassword()
	{
		return dsPassword;
	}

	public void setDsPassword(String dsPassword)
	{
		this.dsPassword = dsPassword;
	}

	public String getYxbz() {
		return yxbz;
	}

	public void setYxbz(String yxbz) {
		this.yxbz = yxbz;
	}

	public long getSortNum() {
		return sortNum;
	}

	public void setSortNum(long sortNum) {
		this.sortNum = sortNum;
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public String getInstanceName() {
		return instanceName;
	}

	public void setInstanceName(String instanceName) {
		this.instanceName = instanceName;
	}

	/**
	 * @return the maxWait
	 */
	public long getMaxWait() {
		return maxWait;
	}

	/**
	 * @param maxWait the maxWait to set
	 */
	public void setMaxWait(long maxWait) {
		this.maxWait = maxWait;
	}

	/**
	 * @return the maxActive
	 */
	public int getMaxActive() {
		return maxActive;
	}

	/**
	 * @param maxActive the maxActive to set
	 */
	public void setMaxActive(int maxActive) {
		this.maxActive = maxActive;
	}
	
}



三、如果要实现动态多数据源的切换需要注意的有两个地方,首先是配置文件:

<!--值得注意的是,这里配置的是中心端的基础数据库-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
 		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />  
  	      <property name="url" value="jdbc:oracle:thin:@192.168.0.21:1521:orcl" />
 		<property name="username" value="yanghao"></property>  
 		<property name="password" value="yanghao"></property>
 		<property name="maxActive" value="50"></property>
 		<property name="validationQuery">   
			<value>SELECT 1 FROM DUAL</value>
		</property>
 	</bean>
<!-- 配置动态数据源 -->
	<bean id="dynamicDataSource" class="com.winning.utils.DynamicDataSource">
		<property name="targetDataSources">
			<map key-type="java.lang.String">
                <entry value-ref="dataSource" key="dataSource"></entry>
            </map> 
		</property>
		<property name="defaultTargetDataSource" ref="dataSource"></property>
	</bean>



四、重写spring的AbstractDataSource



package com.winning.utils;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.ServletContext;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import javax.sql.DataSource;
    
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.jdbc.datasource.AbstractDataSource;
    import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
    import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
    import org.springframework.util.Assert;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import com.winning.bi.domain.sys.SysDataSource;
    import com.winning.bi.domain.sys.SysUser;
    
    public class DynamicDataSource extends AbstractDataSource implements InitializingBean {
    	
    	private Map<Object, Object> targetDataSources;
    
    	private Object defaultTargetDataSource;
    
    	private boolean lenientFallback = true;
    
    	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    
    	private Map<Object, DataSource> resolvedDataSources;
    
    	private DataSource resolvedDefaultDataSource;
    	
    
    	public DataSource getResolvedDefaultDataSource()
    	{
    		return resolvedDefaultDataSource;
    	}
    
    	public void setResolvedDefaultDataSource(DataSource resolvedDefaultDataSource)
    	{
    		this.resolvedDefaultDataSource = resolvedDefaultDataSource;
    	}
    
    	public Map<Object, DataSource> getResolvedDataSources()
    	{
    		return resolvedDataSources;
    	}
    
    	public void setResolvedDataSources(Map<Object, DataSource> resolvedDataSources)
    	{
    		this.resolvedDataSources = resolvedDataSources;
    	}
    
    	public Object getDefaultTargetDataSource()
    	{
    		return defaultTargetDataSource;
    	}
    	
    	public Map<Object, Object> getTargetDataSources()
    	{
    		return targetDataSources;
    	}
    
    
    	/**
    	 * Specify the map of target DataSources, with the lookup key as key.
    	 * The mapped value can either be a corresponding {@link javax.sql.DataSource}
    	 * instance or a data source name String (to be resolved via a
    	 * {@link #setDataSourceLookup DataSourceLookup}).
    	 * <p>The key can be of arbitrary type; this class implements the
    	 * generic lookup process only. The concrete key representation will
    	 * be handled by {@link #resolveSpecifiedLookupKey(Object)} and
    	 * {@link #determineCurrentLookupKey()}.
    	 */
    	public void setTargetDataSources(Map<Object, Object> targetDataSources) {
    		this.targetDataSources = targetDataSources;
    	}
    
    	/**
    	 * Specify the default target DataSource, if any.
    	 * <p>The mapped value can either be a corresponding {@link javax.sql.DataSource}
    	 * instance or a data source name String (to be resolved via a
    	 * {@link #setDataSourceLookup DataSourceLookup}).
    	 * <p>This DataSource will be used as target if none of the keyed
    	 * {@link #setTargetDataSources targetDataSources} match the
    	 * {@link #determineCurrentLookupKey()} current lookup key.
    	 */
    	public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
    		this.defaultTargetDataSource = defaultTargetDataSource;
    	}
    
    	/**
    	 * Specify whether to apply a lenient fallback to the default DataSource
    	 * if no specific DataSource could be found for the current lookup key.
    	 * <p>Default is "true", accepting lookup keys without a corresponding entry
    	 * in the target DataSource map - simply falling back to the default DataSource
    	 * in that case.
    	 * <p>Switch this flag to "false" if you would prefer the fallback to only apply
    	 * if the lookup key was <code>null</code>. Lookup keys without a DataSource
    	 * entry will then lead to an IllegalStateException.
    	 * @see #setTargetDataSources
    	 * @see #setDefaultTargetDataSource
    	 * @see #determineCurrentLookupKey()
    	 */
    	public void setLenientFallback(boolean lenientFallback) {
    		this.lenientFallback = lenientFallback;
    	}
    
    	/**
    	 * Set the DataSourceLookup implementation to use for resolving data source
    	 * name Strings in the {@link #setTargetDataSources targetDataSources} map.
    	 * <p>Default is a {@link JndiDataSourceLookup}, allowing the JNDI names
    	 * of application server DataSources to be specified directly.
    	 */
    	public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
    		this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
    	}
    
    
    	public void afterPropertiesSet() {
    		if (this.targetDataSources == null) {
    			throw new IllegalArgumentException("Property 'targetDataSources' is required");
    		}
    		this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
    		for (Map.Entry entry : this.targetDataSources.entrySet()) {
    			Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
    			DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
    			this.resolvedDataSources.put(lookupKey, dataSource);
    		}
    		if (this.defaultTargetDataSource != null) {
    			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
    		}
    	}
    
    	/**
    	 * Resolve the specified data source object into a DataSource instance.
    	 * <p>The default implementation handles DataSource instances and data source
    	 * names (to be resolved via a {@link #setDataSourceLookup DataSourceLookup}).
    	 * @param dataSource the data source value object as specified in the
    	 * {@link #setTargetDataSources targetDataSources} map
    	 * @return the resolved DataSource (never <code>null</code>)
    	 * @throws IllegalArgumentException in case of an unsupported value type
    	 */
    	public DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
    		if (dataSource instanceof DataSource) {
    			return (DataSource) dataSource;
    		}
    		else if (dataSource instanceof String) {
    			return this.dataSourceLookup.getDataSource((String) dataSource);
    		}
    		else {
    			throw new IllegalArgumentException(
    					"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
    		}
    	}
    
    
    	public Connection getConnection() throws SQLException {
    		 Connection concretion = determineTargetDataSource().getConnection();
    		 return concretion;
    	}
    
    	public Connection getConnection(String username, String password) throws SQLException {
    		return determineTargetDataSource().getConnection(username, password);
    	}
    
    	/**
    	 * 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;
    	}
    
    
    	/**
    	 * Resolve the given lookup key object, as specified in the
    	 * {@link #setTargetDataSources targetDataSources} map, into
    	 * the actual lookup key to be used for matching with the
    	 * {@link #determineCurrentLookupKey() current lookup key}.
    	 * <p>The default implementation simply returns the given key as-is.
    	 * @param lookupKey the lookup key object as specified by the user
    	 * @return the lookup key as needed for matching
    	 */
    	public Object resolveSpecifiedLookupKey(Object lookupKey) {
    		return lookupKey;
    	}
    	
        
        /**   
         * @作者: yanghao 
         * @时间: Nov 4, 2015 10:27:43 AM 
         * @描述: TODO
         * @return
         * @备注:  
        */
        public static String getCustomerType() {
        	return HttpSessionUtil.getDataSourceId();
        }
        
        
        public static void clearCustomerType() {
        }
        
        protected Object determineCurrentLookupKey() {
        	return getCustomerType();
        }
    }



/**
     * <p> @Project:  </p>
     * <p> @Package: com.winning.utils </p>
     * <p> @Company:   </p>
     * <p> Copyright ©2017 Tech-Winning. All rights reserved </p>
     * 
     * <p> @author YangHao </p>
     * <p> @version V3.0 </p>
     * <p> @Createdate Mar 28, 2017 4:53:07 PM </p>
     *
     * <p> @Modification  History 
     *     Date             Author        Version        Description
     *     -----------------------------------------------------------------------------------
     *     Mar 28, 2017                  YangHao       1.0            1.0 </p>
     * <p> Why & What is modified </p>
     */
    package com.winning.utils;
    
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import flex.messaging.FlexContext;
    
    
    /**
     * 
     *
     *HttpSessionUtil.java 文件使用说明
     * 说明:XXXX<br/>
     *  
     * 公开方法:
     * <ul>
     *      <li>getDataSourceId: 获取数据源ID</li>
     *      <li>sendDataSourceIdToCookie:将数据源ID保存到Cookie中</li>
     * </ul>
     *
     * @version ver 4.0.0
     * @author Shanghai Kinstar Winning Software .co.ltd. pcdalao
     * @since 作成日期:May 14, 2017(XCH)<br/>
     *        改修日期:
     */
    public class HttpSessionUtil {
    
    	private static Logger logger = LoggerFactory.getLogger(HttpSessionUtil.class);
    
    	/**
    	 *  获取数据源ID,
    	 * @return 数据源ID
    	 * @author: XCH
    	 */
    	public static String getDataSourceId() {
    		HttpServletRequest req=getRequest();
    		if(req==null){
    			return null;
    		}
    		Cookie[] cookies = req.getCookies();
    		
    		if(cookies==null ||cookies.length == 0){
    			return null;
    		}
    		
    		for (Cookie eleCookie : cookies) {
    			if (Constant.COOKIDDBSID.equals(eleCookie.getName())) {
    				logger.debug("COOKIDDBSID:{}",new String[]{eleCookie.getValue()});
    				/**cookie 数据源ID,字符null  代表基础库,基础库为默认数据源,返回null  */
    				if(StringUtil.isEmptyOfStrict(eleCookie.getValue())){
    					return null;
    				}
    				return eleCookie.getValue();
    			}
    		}
    		return null;
    	}
    	
    	private static HttpServletRequest getRequest(){
    		if(RequestContextHolder.getRequestAttributes()==null){
    			return null;
    		}
    		return	((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
    	}
    	
    	
    	/**
    	 * <p> 保存DataSourceID 到Cookie 中 </p>
    	 * @param dataSourceID   待保存到浏览器Cookie的数据源ID
    	 * @author: XCH
    	 */
    	public static void sendDataSourceIdToCookie( String dataSourceID) {
    		HttpServletResponse response=FlexContext.getHttpResponse();
    		if(response!=null){
    			response.addCookie(new Cookie(Constant.COOKIDDBSID, dataSourceID));
    		}
    	}
    
    }



五、登录的时候,首先根据用户ID查找到该用户对应的数据源信息,调用以下方法并将数据源的ID传入,将数据源的信息存入cookie中,以后每次建立数据库连接,

就从cookie中获取。

HttpSessionUtil.sendDataSourceIdToCookie(dataSourceId);