前段时间我们的系统接到新增多一个频道的需求,原本我们的系统只是针对于广州的业务,现在需要新增另一个城市上海,经过和产品人员沟通和分析,城市之间的业务逻辑除了一些小差异基本还是一样的,数据库的结构经过整合两个城市也可以达到一样的结构,但上海需要独立出另一个数据库.
我们以前发布器的做法是用作为方法的一个参数由调用者一直传到访问对象(索引或数据库),虽然这种做法一样可以很快的实现,但是将数据库,索引的选择和业务逻辑混搭在一起的设计在感觉上是比较混乱,并且不利于将来多个城市(频道)的建立,所以选了通过ThreadLocal来实现多数据源的动态切换.
ThreadLocal 是一个依赖于执行线程的存储器,对它就只有简单的一个set和get方法,不同线程之间是相互独立的。简单地讲,就是:这个线程set了一个对象入去,只有这个线程自己可以把它get出来,其它线程是get不出来的。
好了,下面是具体显示的方式
首先定义一个filter,通过filter取得域名,因为我们的域名中带有城市的标志,如广州是http://gz.***.com,上海是http://sh.***.com,通过取得的域名,我们取得城市的表示放进ThreadLocal.set(city);
public class DataSourceFilter extends HttpServlet implements Filter {

public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain filterChain) {
      HttpServletRequest req = (HttpServletRequest) request;
      String servername = req.getServerName();
      SpObserver.putCityByDomain(servername);
      filterChain.doFilter(request, response);
  }

}
public class SpObserver {
    private static ThreadLocal<String> local = new ThreadLocal<String>();    

    public static void putCityByDomain(String domain) {
        String city = publicconfig.getCityMap().get(domain);//拆分domain,获取城市名
        local.set(city);
    }
    public static String getCity() {
        String city = (String) local.get();
        return city;
    }

} 
建立多个与之对应的数据源
    <bean id="atomDataSource_gz"
        class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init"
        destroy-method="close">
        <property name="uniqueResourceName">
            <value>mysql/gz</value>
        </property>
        <property name="xaDataSourceClassName">
            <value>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</value>
        </property>
        <property name="xaProperties">
            <props>
                <prop key="URL"><![CDATA[${jdbc_gz.url}]]></prop>
                <prop key="user"><![CDATA[${jdbc_gz.username}]]></prop>
                <prop key="password"><![CDATA[${jdbc_gz.password}]]></prop>
            </props>
        </property>
        <property name="maxPoolSize">
            <value>50</value>
        </property>
        <property name="minPoolSize">
            <value>5</value>
        </property>
        <property name="loginTimeout">
            <value>20</value>
        </property>
        <property name="testQuery">
            <value>SELECT 1</value>
        </property>
    </bean>
<bean id="atomDataSource_sh"
        class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init"
        destroy-method="close">
        <property name="uniqueResourceName">
            <value>mysql/sh</value>
        </property>
        <property name="xaDataSourceClassName">
            <value>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</value>
        </property>
        <property name="xaProperties">
            <props>
                <prop key="URL"><![CDATA[${jdbc_sh.url}]]></prop>
                <prop key="user"><![CDATA[${jdbc_sh.username}]]></prop>
                <prop key="password"><![CDATA[${jdbc_sh.password}]]></prop>
            </props>
        </property>
        <property name="maxPoolSize">
            <value>50</value>
        </property>
        <property name="minPoolSize">
            <value>5</value>
        </property>
        <property name="loginTimeout">
            <value>20</value>
        </property>
        <property name="testQuery">
            <value>SELECT 1</value>
        </property>
    </bean>
    <bean id="dataSource" class="com.***.shine.constant.MultiDataSource">  
        <property name="dataSource" ref="atomDataSource_gz" /> <!-- 默认城市为gz -->
    </bean> 
public class MultiDataSource extends AtomikosDataSourceBean implements ApplicationContextAware {
    private ApplicationContext applicationContext = null;
    private DataSource dataSource = null;
    public Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }
    public Connection getConnection(String arg0, String arg1)
            throws SQLException {
        return getDataSource().getConnection(arg0, arg1);
    }
     ..
..
    
    //通过适配者的设计模式动态的切换实现类,这样就实现了在DataSourceBean中,我们是要注入atomDataSource_gz还是atomDataSource_sh
    public DataSource getDataSource(String dataSourceName) {
        try{
            if(dataSourceName==null||dataSourceName.equals("")){
                return this.dataSource;
            }
            return (DataSource)this.applicationContext.getBean(dataSourceName);
        }catch(NoSuchBeanDefinitionException ex){
            throw new DaoException("There is not the dataSource <name:"+dataSourceName+"> in the applicationContext!");
        }
    }
    
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    //主要是下面这一段,通过SpObserver.getCity() 获取相应的城市(频道)名字
    public DataSource getDataSource(){
        String city = SpObserver.getCity();
        if(city == null || city.equals("") || city.equals("null"))
            city = "gz";
        return getDataSource("atomDataSource_"+city);
    }
    ...
}
这样在各个层中的实现我们就不需去关注究竟是哪个城市的接口,因为每个请求都附带了ThreadLocal的相应信息
关于ThreadLocal具体可看-->
通通透透理解ThreadLocal