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環境如下:
其中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的切分了。