sping多数据源的动态切换和更新


         当时想写没有动笔是因为,动态数据源切换解决了,但是无法做到动态添加,添加新的数据源需要重启web服务器。后来这个问题解决了,但因为业务忙,今日动笔,为了写这篇文章一周没关电脑了。

         公司的业务是这样的,有目前有将近150多个db,每个的db的数据库库结构一致。位于同一台机器的不同端口下。现在需要做个管理系统,能管理这些db。并且这些db的数目在不断的增长中。不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在spring框架中总是配置一个数据源,如何在DAO在访问sessionFactory的时候在多个数据源中不断切换。

        面对这个的需求的要解决的技术问题有:

        1.这些db信息要如何存储,如何用spring的数据源进行管理。

        2.如何做到并发,不同人登录处理不同的db数据,也就是数据源的动态切换。

        3.当添加新的数据源时,如何动态的托管给spring。


方案:

1.首先这些db的数据,一定是要存储到db中的,当有新的db信息时,可以让运维的同学添加。如果是在xml中,容易出错不说,动态load那些db信息的xml生效也是可以的应该做的。

2.如何做的动态切换方案大概是这样,首先用ThreadLocal记录下不同人选择的服务器,然后实现spring的AbstractDataSource类,在这个类中加入根据前台选择服务器来动态返回数据源的逻辑。

3.动态更改数据源的方法是:首先实现AbstractDataSource实现类的已注册数据源,将最新的数据源信息从db中查出,set到AbstractDataSource中注册的数据源列表中。


践行:

        起初考虑用 Spring 的 AbstractRoutingDataSource 实现数据源切换,能切换但不能动态改变数据库,初始化完成后就不能动了。
        设想,如果Spring的事务不关心AbstractRoutingDataSource里数据源的变动,那可以直接实现一个和AbstractRoutingDataSource功能相仿,并且能动态改动数据源的 AbstractRoutingDataSource。

        自己构建一个 AbstractRoutingDataSource (名字和Spring提供的一样)
        Spring 继承了 AbstractDataSource并实现接口InitializingBean.
        实现接口InitializingBean其实是为了把用户设置进去的 datasource 的Map转换成自己的Map。所以才导致 后面再设置 DataSource Map 不能生效.
        那我们现在就写一个 AbstractRoutingDataSource ,也继承 AbstractDataSource,但是不用实现InitializingBean了,而后再自己实现的AbstractRoutingDataSource的子类里去实现InitializingBean.

      MyAbstractRoutingDataSource.java

     

public abstract class MyAbstractRoutingDataSource extends AbstractDataSource{
	
	private boolean lenientFallback = true;
	
	private Map<Object, DataSource> resolvedDataSources;
	
	private DataSource resolvedDefaultDataSource;
	
	private org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(MyAbstractRoutingDataSource.class);
					   

	public Connection getConnection() throws SQLException {
		// TODO Auto-generated method stub
		return determineTargetDataSource().getConnection();
	}

	public Connection getConnection(String username, String password)
			throws SQLException {
		// TODO Auto-generated method stub
		return determineTargetDataSource().getConnection(username, password);
		
	}

	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		// TODO Auto-generated method stub
		return null;
	}
	
	public void putNewDataSource(Object key, DataSource dataSource){
		if(this.resolvedDataSources == null){
			this.resolvedDataSources = new HashMap<Object, DataSource>();
		}
		if(this.resolvedDataSources.containsKey(key)){
			this.resolvedDataSources.remove(key);
			logger.info("remove old key:" + key);
		}
		logger.info("add key:" + key + ", value=" + dataSource);
		this.resolvedDataSources.put(key, dataSource);
	}
	
	/**
	 * 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();
		int index = 0;
		for (Entry<Object, DataSource> element : resolvedDataSources.entrySet()) {
			logger.debug("myAbstractDS, index:" + index + ", key:" + element.getKey() + ", value:" + element.getValue().toString());
			index++;
		}
		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 + "]");
		}
		logger.info("myAbstractDS, hit DS is " + dataSource.toString());
		return dataSource;
	}
	
	protected abstract Object determineCurrentLookupKey();

	public boolean isLenientFallback() {
		return lenientFallback;
	}

	public void setLenientFallback(boolean lenientFallback) {
		this.lenientFallback = lenientFallback;
	}

	public Map<Object, DataSource> getResolvedDataSources() {
		return resolvedDataSources;
	}

	public void setResolvedDataSources(Map<Object, DataSource> resolvedDataSources) {
		this.resolvedDataSources = resolvedDataSources;
	}
	
	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		if (iface.isInstance(this)){
			return (T) this;
		}
		return determineTargetDataSource().unwrap(iface);
	}

	public DataSource getResolvedDefaultDataSource() {
		return resolvedDefaultDataSource;
	}

	public void setResolvedDefaultDataSource(DataSource resolvedDefaultDataSource) {
		this.resolvedDefaultDataSource = resolvedDefaultDataSource;
	}
	
}

MyRoutingDataSource.java

public class MyRoutingDataSource extends MyAbstractRoutingDataSource implements InitializingBean, ApplicationContextAware{
	
	private static final Logger logger = Logger.getLogger(MyRoutingDataSource.class);
	
	@Resource(name = "playerServerInfoConstant")
	private PlayerServerInfoConstant playerServerInfoConstant;
	
	private ApplicationContext applicationContext = null;

	@Override
	protected Object determineCurrentLookupKey() {
		String dataSourceName = SpObserver.getSp();
		logger.info("dynamic data source name:" + dataSourceName);
		if(dataSourceName == null){
			dataSourceName = "dataSource2";
		}
		return dataSourceName;
	}
	
	public DataSource getDataSource() throws PropertyVetoException{
		String sp = SpObserver.getSp();
		return getDataSource(sp);
	}
	
	private DataSource getDataSource(String datasourceName) throws PropertyVetoException {
		Map<Object, DataSource> resolvedDataSources = this.getResolvedDataSources();
		if(resolvedDataSources == null){
			resolvedDataSources =  playerServerInfoConstant.getServerMap(false);
		}
		
		if (datasourceName == null || "".equals(datasourceName)) {
			return getResolvedDefaultDataSource();
		}
		DataSource source = resolvedDataSources.get(datasourceName);
		if (source == null) {
			return getResolvedDefaultDataSource();
		}
		return source;
	}

	public void afterPropertiesSet() throws Exception {
		Map<Object, DataSource> resolvedDataSources = playerServerInfoConstant.getServerMap(false);;
		int i =0;
		for (Entry<Object, DataSource> element : resolvedDataSources.entrySet()) {
			logger.debug("myAbstractDS, index:" + i + ", key:" + element.getKey() + ", value:" + element.getValue().toString());
			i++;
		}
		this.setResolvedDataSources(resolvedDataSources);
		this.setResolvedDefaultDataSource((DataSource)this.applicationContext.getBean("dataSource"));
		
	}

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

}

上面代码中:

1、做一个filter,每次客户发出请求的时候就调用SpObserver.petSp(dataSourceName),将request中的dataSourceName传递给SpObserver对象。最后修改MultiDataSource的方法getDataSource()。

MyFilter.java

public class MyFilter implements Filter {

	public MyFilter() {
	}

	public void init(FilterConfig filterConfig) throws ServletException {
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		Integer dataSourceIdx = (Integer)httpRequest.getSession().getAttribute(AppConstant.SESSION_DEAULT_SERVER_IDX_KEY);
		if(dataSourceIdx == null || dataSourceIdx == -1){
			//the test server's idx is 2
			dataSourceIdx = 2;
		}
		SpObserver.putSp("dataSource" + dataSourceIdx);
		chain.doFilter(request, response);
	}

	public void destroy() {

	}

}

SpOvserver.java

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

2、构造一个playerServerConstant从库中取db的信息。

PlayerServerConstant.java

public class PlayerServerInfoConstant  implements ApplicationListener{
	
	@Autowired
	private GameServerInfoService gameServerService;
	
	private ConcurrentHashMap<Object, DataSource> allDataSources = null;
	
	private Logger logger = Logger.getLogger(PlayerServerInfoConstant.class);
	
	private PlayerServerInfoConstant(){}
	
	public Map<Object, DataSource> getServerMap(boolean refreshflag) throws PropertyVetoException{
		 if(allDataSources == null || refreshflag == true){  
	            //synchronized(serverMap){
	                //if(serverMap == null){  
			 allDataSources = new ConcurrentHashMap<Object, DataSource>();
			 for (GameServerInfo info : gameServerService.getGameServers()) {
        		ComboPooledDataSource cpds = new ComboPooledDataSource();
        		cpds.setDriverClass("com.mysql.jdbc.Driver");
        		cpds.setJdbcUrl("jdbc:mysql://" + info.getIp() + ":" + info.getPort() + "/" + info.getDb() + "?useUnicode=true&characterEncoding=UTF-8");
        		cpds.setUser(info.getUsername());
        		cpds.setPassword(info.getPassword());
        		cpds.setTestConnectionOnCheckout(true);
        		logger.info("init dataSouce:" + info.getIp() + "   " + info.getPort());
        		allDataSources.put("dataSource" + info.getIdx(), cpds);
			 }
	               // }  
	            //}  
	      }  
	      return allDataSources;  
	}
	
	public void empytDataSources(){
		
	}

	public void onApplicationEvent(ApplicationEvent arg0) {
		try {
			getServerMap(false);
		} catch (PropertyVetoException e) {
			e.printStackTrace();
		}
	}
	

}

在dao中SeesionFactory的dataSource如何构造呢?

<bean id="gameServerDataSource" class="com.youai.gamemis.model.dao.MyRoutingDataSource">
		<property name="resolvedDataSources">
              <map key-type="java.lang.String">
                 <entry key="dataSource2" value-ref="dataSource"/>
              </map>
        </property>
        <property name="resolvedDefaultDataSource" ref="dataSource"/>
	</bean>

动态更新数据源的方式:

ConcurrentHashMap<Object, DataSource> newDataSource = new ConcurrentHashMap<Object, DataSource>(playerServerInfoConstant.getServerMap(true));
        
        //modify dataSource at dataSourceConstant
        
        BeanDefinitionBuilder userBeanDefinitionBuilder = BeanDefinitionBuilder 
                .genericBeanDefinition(PlayerServerInfoConstant.class);  
        userBeanDefinitionBuilder.addPropertyValue("gameServerService", gameServerService);  
        userBeanDefinitionBuilder.addPropertyValue("allDataSources", newDataSource);  
        BeanDefinition bd = userBeanDefinitionBuilder.getRawBeanDefinition();
        bd.setScope("singleton");
        beanFactory.registerBeanDefinition("playerServerInfoConstant", bd);
        
        //refresh data at router
        for (Entry<Object, DataSource> element : newDataSource.entrySet()) {
        	gameServerDataSource.putNewDataSource(element.getKey(), element.getValue());
	}

好像很乱的样子,有问题,欢迎讨论。

ref:http://www.wzero.net/?p=62

     http://www.iteye.com/topic/72486

     http://westerly.blog.51cto.com/1077430/638818

     http://blog.csdn.net/buyaore_wo/article/details/8119577




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值