近日應一個小學弟的要求給他講解個Spring+Hibernate下多資料來源動態切換的功能,他的需求就是在登入時候,選擇或者填寫)自己的資料庫名稱,然後登入完畢後,會話就是基於這個資料來源串連的,在會話期間允許改變自己的資料庫連接。正好之前也沒做過類似的實驗,於是就寫了個小小的樣本測試下。
一種最簡單的方法是在Spring的設定檔上配置多個資料來源,但這種方式其實和單個資料來源的使用沒什麼差別,都是使用預先定義好了的資料來源。所以這種方法並沒有解決我所遇到的問題。
然後我就想通過在需要切換資料來源時構造Configuration來配置資料來源,但是仔細想想這種代價應當是比較高的。也就果斷放棄了。
後來想想,在第一種方案中,問題主要是出在資料來源是預先定義的,那麼我把預先定義的資料來源抽取出來,採用一個工廠方式或者緩衝方式來產生資料來源,這樣不就可以滿足需求了嗎?
這個問題的關鍵點是
1.當需要資料來源的切換操作時 應當是在什麼時候進行資料來源更改的設定,
2 如果更改了資料連線設定,應當怎樣反映到資料來源上。
3 保認證資料來源的更改對資料操作是透明的
針對第一個問題,我採用的是一個Filter的方式來解決的。在每次請求中過濾Session中的dataBase參數,當第一次會話請求時,這時候Session還沒有存在,此時採用預設的資料庫,預設資料庫的作用是用來儲存登入用的使用者名稱和密碼),並把預設的資料庫名稱附加到當前線程中,等待datasource中的讀取。如果不是第一次的請求,這時候session是存在的了,那麼就應當能從session中取database參數,然後同樣附加到當前線程,等到datasource的讀取。這樣的話,就是當在頁面上更改了資料庫,然後點擊提交,在提交這個動作中仍然串連的是當前的資料來源,但是提交動作完成後既經過filter處理後),下一次再有資料庫操作時,就會用上新的資料來源了。下面是Filter的代碼
- package cn.swjtu.multisource.tools;
-
- import java.io.IOException;
-
- import javax.faces.context.FacesContext;
- 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 com.apusic.web.session.ContextSession;
-
- public class MyFilter implements Filter {
-
- @Override
- public void destroy() {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- String database = null;
- if (FacesContext.getCurrentInstance() != null) {
- ContextSession session = (ContextSession) FacesContext
- .getCurrentInstance().getExternalContext().getSession(true);
- database = (String) session.getAttribute(ConstArgs.KEY_DATABASE);
- }
- if (database == null && "".equals(database)) {
- database = ConstArgs.DEFAULT_DATABASE;
- }
- ThreadLocalHolder.set(database);
- chain.doFilter(request, response);
- ThreadLocalHolder.remove();
- }
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- // TODO Auto-generated method stub
-
- }
-
- }
第二個問題的解決方式 是自己實現個datasource,並在該datasource中進行資料來源緩衝,這樣就可以不必為之前用到的資料來源在進行新的建立操作的,但是資料來源的混存應當是有個最大值的保證不佔用太多的資料庫連接資源),我在這個樣本中還沒有設定。然後當是新串連時就建立個資料來源並緩衝,把對該datasource的所有操作方都轉寄到建立並緩衝的這個資料來源上,如果訪問的是已經存在資料來源就從緩衝中取出,同樣也是把對該datasource的所有操作方都轉寄到緩衝的這個資料來源上。下面是實現的datasource的代碼:
- package cn.swjtu.multisource.tools;
-
- import java.io.PrintWriter;
- import java.sql.Connection;
- import java.sql.SQLException;
- import java.util.HashMap;
- import java.util.Map;
-
- import javax.faces.context.FacesContext;
- import javax.sql.DataSource;
-
- import org.springframework.jdbc.datasource.SingleConnectionDataSource;
-
- import com.apusic.web.session.ContextSession;
-
- public class DataSourceWithCache implements DataSource {
-
- private static Map<String, DataSource> sources = new HashMap<String, DataSource>();
-
- // private DataSource source;
- private DataSource getDataSource() {
- String name = ThreadLocalHolder.get();
- if (name == null || "".equals(name)) {
- if(FacesContext.getCurrentInstance()!=null){
- name = (String) ((ContextSession) FacesContext.getCurrentInstance()
- .getExternalContext().getSession(true))
- .getAttribute(ConstArgs.KEY_DATABASE);
- }
- if (name == null || "".equals(name)) {
- name = ConstArgs.DEFAULT_DATABASE;
- }
- }
- DataSource source = sources.get(name);
- if (source == null) {
- source = createSource(name);
- sources.put(name, source);
- }
- return source;
- }
-
- private DataSource createSource(String name) {
- SingleConnectionDataSource source = new SingleConnectionDataSource();
- source.setDriverClassName(ConstArgs.DRIVER_CLASS);
- source.setUrl(ConstArgs.SERVER_URL + name);
- source.setUsername(ConstArgs.USER);
- source.setPassword(ConstArgs.PASSWORD);
- source.setSuppressClose(true);
- return source;
- }
-
- @Override
- public Connection getConnection() throws SQLException {
- return getDataSource().getConnection();
- }
-
- @Override
- public Connection getConnection(String username, String password)
- throws SQLException {
- return getDataSource().getConnection(username, password);
- }
-
- @Override
- public PrintWriter getLogWriter() throws SQLException {
- return getDataSource().getLogWriter();
- }
-
- @Override
- public int getLoginTimeout() throws SQLException {
- return getDataSource().getLoginTimeout();
- }
-
- @Override
- public void setLogWriter(PrintWriter out) throws SQLException {
- getDataSource().setLogWriter(out);
-
- }
-
- @Override
- public void setLoginTimeout(int seconds) throws SQLException {
- getDataSource().setLoginTimeout(seconds);
- }
-
- @Override
- public boolean isWrapperFor(Class<?> iface) throws SQLException {
- return getDataSource().isWrapperFor(iface);
- }
-
- @Override
- public <T> T unwrap(Class<T> iface) throws SQLException {
- return getDataSource().unwrap(iface);
- }
-
- }
這樣所有的更改並沒有涉及資料庫操作層的代碼,可以完全保證對資料庫操作層的透明性。
該工程採用的Operamasks開源社區的提供的整合開發工具,這裡只能發2M的文檔,所以我把樣本工程發這裡 點擊下載
本文出自 “On_The_Way” 部落格,請務必保留此出處http://westerly.blog.51cto.com/1077430/638818