Appfuse與DB Cluster

寫程式如果有遇過DB Cluster,就會大概知道,程式必須切分為Read Connection與Write Connection,切分的意義在於避免寫入資料庫時,在不同的DB Server上寫到相同的資料。

舉例來說,有二個使用者同時上傳一個新的商品,而我們商品的ID是設定為auto_increment,所以是由DB來控管資料的ID,但是這二個使用者是同時新增的,如果程式是寫到不同的二個DB,寫入的資料再由Cluster互相傳遞到其他DB,這樣就有機會遇到:有二個相同的ID,卻是不同的資料。

了解到為何要切分後,我們回到程式面來看,我們如何在Appfuse這個範本網站中,切分這二個Connection呢?

DB Cluster環境

在我們講解設定前,我們必須先定義好DB Cluster的環境,假設架設好的DB Cluster環境如下:

Untitled.png

其中Load Balancer是利用HA Proxy架設的,設定如下:

所以我們可以知道它是利用Port來區隔的,其中Port 13304代表的是Write connection,13305代是的是Read Connection。

詳細可參考另外一篇 MariaDB Cluster 架設

設定的部份

切分這二個Connection時,有不少部份需要調整,我們首先來看設定的部份。

pom的設定

一開始Appfuse中的POM大致如下:

只有一個jdbc.url;為了切分Connection,我們要把jdbc.url分成二個,如下:

我們變更了jdbc.url的IP與Port,讓jdbc.url連到Read Connection,並且新增了一個設定是jdbc.write.url,連到Write Connection。

jdbc.properties的設定

別忘了,pom中的設定是為了方便在編譯程式時,方便替換properties中的設定而已,實際上程式吃的還properties中的設定,而jdbc.url是被設定在jdbc.properties中的,所以我們也要在jdbc.properties中設定一組jdbc.write.url來接替pom.xml中的設定,如下:

applicationContext-resources.xml的設定

jdbc.properties中的設定,到最後會被用在applicationContext-resources.xml,所以我們先來看看applicationContext-resources.xml中的內容:

可以看到上一層把jdbc.properties吃進來後,而在下方宣告一個dataSource。這個dataSource是Read Connection的(因為它吃的是jdbc.url),所以我們要宣告一個writeDataSource,吃jdbc.write.url的設定,如下圖:

applicationContext-dao.xml的設定

applicationContext-resources.xml中宣告的dataSource,是在applicationContext-dao.xml中被使用的,所以接下來我們要修改applicationContext-dao.xml,原本的設定為:

為了方便區隔,我們將sessionFactory修改為readSessionFactory,再宣告一個writeSessionFactory,如下圖:

變更完後,在這個檔案的下方有一個transactionManager用到sessionFactory,如下圖:

因為這個部份是宣告Transaction用的,因為Read Connection不需要Transaction,只需要用在Write Connection即可,所以我們將它的ref改為writeSessionFactory,如下圖:

程式的部份

我們把設定的部份調整完了後,再來開始要調整程式了。

GenericDaoHibernate.java

程式原本的部份,有一個sessionFactory,如下圖:

因為我們變更了sessionFactory ,而且加了一個writeSessionFactory,所以要把原本的sessionFactory刪除,變成:

變更之後,會出現紅字的錯誤,所以要一個一個修改,下圖是建構子修改後的結果:

再來是set與get的部份,因為原本sessionFactory的set與get都會有問題,所以把它刪除,即下圖的程式先刪除:

新增下方的程式:

public Session getReadSession() throws HibernateException {

Session sess = getWriteSessionFactory().getCurrentSession();

if (sess == null) {

sess = getWriteSessionFactory().openSession();

sess.setDefaultReadOnly(true);

}

return sess;

}

 

public Session getWriteSession() throws HibernateException {

Session sess = getWriteSessionFactory().getCurrentSession();

if (sess == null) {

sess = getWriteSessionFactory().openSession();

}

return sess;

}

 

@Autowired

@Required

public SessionFactory getReadSessionFactory() {

return readSessionFactory;

}

 

public void setReadSessionFactory(SessionFactory readSessionFactory) {

this.readSessionFactory = readSessionFactory;

}

 

@Autowired

@Required

public SessionFactory getWriteSessionFactory() {

return writeSessionFactory;

}

 

public void setWriteSessionFactory(SessionFactory writeSessionFactory) {

this.writeSessionFactory = writeSessionFactory;

}

新增完後如下圖:

可以看到仍有一些錯誤,不過從這裡開始的錯誤,就需要人工來判斷了,判斷這個部份要使用Read Connection或是Write Connection,以上圖的getAll及search這二個Method來說,都是要使用Read Connection,所以會變成:

依據這個基準,把所有的紅字修改,需要Write Connection的,則呼叫getWriteSession(),如下圖:

LookupDaoHibernate.java

這支程式有比較特別,因為它並沒有繼承GenericDaoHibernate.java,所以要抓出來額外處理。原本程式:

修改為:

其他程式的紅字

我們變更完上方的程式後,可以看到所有*DaoHibernate.java都會出現紅字,不過這些紅字的原因都是一樣的,一個一個人工過濾,看是需要Read Connection或Write Connection;另外要注意的是,程式裡面有些是呼叫getSessionFactory後,再呼叫session,因為這樣可能會有問題,所以建議直接改為呼叫getReadSession或getWriteSession,而不是去呼叫getReadSessionFactory或getWriteSessionFactory;例如RoleDaoHibernate.java中的removeRole,原本是:

請改為:

不過UserDaoHibernate.java中的getUserPassword還是維持getReadSessionFactory,因為它要直接使用JdbcTemplate,所以修改後的如下:

測試

最後完成修改後,重新啟動程式,進入登入與新增使用者,如果都沒有問題,即代表修改成功了(如果沒有DB Cluster環境,可先將jdbc.write.url設定成跟jdbc.url一樣)。

讀取沒有問題。

寫入也OK。


這樣我們的程式就已經完成connection的切分了。

arrow
arrow

    JAVA Programmer 發表在 痞客邦 留言(0) 人氣()