前段時(shí)間我們的系統(tǒng)接到新增多一個(gè)頻道的需求,原本我們的系統(tǒng)只是針對(duì)于廣州的業(yè)務(wù),現(xiàn)在需要新增另一個(gè)城市上海,經(jīng)過(guò)和產(chǎn)品人員溝通和分析,城市之間的業(yè)務(wù)邏輯除了一些小差異基本還是一樣的,數(shù)據(jù)庫(kù)的結(jié)構(gòu)經(jīng)過(guò)整合兩個(gè)城市也可以達(dá)到一樣的結(jié)構(gòu),但上海需要獨(dú)立出另一個(gè)數(shù)據(jù)庫(kù).
我們以前發(fā)布器的做法是用作為方法的一個(gè)參數(shù)由調(diào)用者一直傳到訪問(wèn)對(duì)象(索引或數(shù)據(jù)庫(kù)),雖然這種做法一樣可以很快的實(shí)現(xiàn),但是將數(shù)據(jù)庫(kù),索引的選擇和業(yè)務(wù)邏輯混搭在一起的設(shè)計(jì)在感覺(jué)上是比較混亂,并且不利于將來(lái)多個(gè)城市(頻道)的建立,所以選了通過(guò)ThreadLocal來(lái)實(shí)現(xiàn)多數(shù)據(jù)源的動(dòng)態(tài)切換.
ThreadLocal 是一個(gè)依賴(lài)于執(zhí)行線程的存儲(chǔ)器,對(duì)它就只有簡(jiǎn)單的一個(gè)set和get方法,不同線程之間是相互獨(dú)立的。簡(jiǎn)單地講,就是:這個(gè)線程set了一個(gè)對(duì)象入去,只有這個(gè)線程自己可以把它get出來(lái),其它線程是get不出來(lái)的。
好了,下面是具體顯示的方式
首先定義一個(gè)filter,通過(guò)filter取得域名,因?yàn)槲覀兊挠蛎袔в谐鞘械臉?biāo)志,如廣州是http://gz.***.com,上海是http://sh.***.com,通過(guò)取得的域名,我們?nèi)〉贸鞘械谋硎痉胚M(jìn)ThreadLocal.set(city);
建立多個(gè)與之對(duì)應(yīng)的數(shù)據(jù)源
<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" /> <!-- 默認(rèn)城市為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);
}
..
//通過(guò)適配者的設(shè)計(jì)模式動(dòng)態(tài)的切換實(shí)現(xiàn)類(lèi),這樣就實(shí)現(xiàn)了在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;
}
//主要是下面這一段,通過(guò)SpObserver.getCity() 獲取相應(yīng)的城市(頻道)名字
public DataSource getDataSource(){
String city = SpObserver.getCity();
if(city == null || city.equals("") || city.equals("null"))
city = "gz";
return getDataSource("atomDataSource_"+city);
}
...
}
這樣在各個(gè)層中的實(shí)現(xiàn)我們就不需去關(guān)注究竟是哪個(gè)城市的接口,因?yàn)槊總€(gè)請(qǐng)求都附帶了ThreadLocal的相應(yīng)信息
關(guān)于ThreadLocal具體可看-->通通透透理解ThreadLocal
我們以前發(fā)布器的做法是用作為方法的一個(gè)參數(shù)由調(diào)用者一直傳到訪問(wèn)對(duì)象(索引或數(shù)據(jù)庫(kù)),雖然這種做法一樣可以很快的實(shí)現(xiàn),但是將數(shù)據(jù)庫(kù),索引的選擇和業(yè)務(wù)邏輯混搭在一起的設(shè)計(jì)在感覺(jué)上是比較混亂,并且不利于將來(lái)多個(gè)城市(頻道)的建立,所以選了通過(guò)ThreadLocal來(lái)實(shí)現(xiàn)多數(shù)據(jù)源的動(dòng)態(tài)切換.
ThreadLocal 是一個(gè)依賴(lài)于執(zhí)行線程的存儲(chǔ)器,對(duì)它就只有簡(jiǎn)單的一個(gè)set和get方法,不同線程之間是相互獨(dú)立的。簡(jiǎn)單地講,就是:這個(gè)線程set了一個(gè)對(duì)象入去,只有這個(gè)線程自己可以把它get出來(lái),其它線程是get不出來(lái)的。
好了,下面是具體顯示的方式
首先定義一個(gè)filter,通過(guò)filter取得域名,因?yàn)槲覀兊挠蛎袔в谐鞘械臉?biāo)志,如廣州是http://gz.***.com,上海是http://sh.***.com,通過(guò)取得的域名,我們?nèi)〉贸鞘械谋硎痉胚M(jìn)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;
}

}

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

}
建立多個(gè)與之對(duì)應(yīng)的數(shù)據(jù)源
<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" /> <!-- 默認(rèn)城市為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);
}

//通過(guò)適配者的設(shè)計(jì)模式動(dòng)態(tài)的切換實(shí)現(xiàn)類(lèi),這樣就實(shí)現(xiàn)了在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;
}
//主要是下面這一段,通過(guò)SpObserver.getCity() 獲取相應(yīng)的城市(頻道)名字
public DataSource getDataSource(){
String city = SpObserver.getCity();
if(city == null || city.equals("") || city.equals("null"))
city = "gz";
return getDataSource("atomDataSource_"+city);
}
...
}
這樣在各個(gè)層中的實(shí)現(xiàn)我們就不需去關(guān)注究竟是哪個(gè)城市的接口,因?yàn)槊總€(gè)請(qǐng)求都附帶了ThreadLocal的相應(yīng)信息
關(guān)于ThreadLocal具體可看-->通通透透理解ThreadLocal