會有這樣子的心情跟感受,有三:
- 有人說,程式設計是很無趣的 ("好像"不是很難、不是很有價值的工作)。
- 技術門檻越來越低,從業的人越來越多;而且做出來的軟體系統,品質並不高。(原因可能不只是"人"的部份而已)
- 經歷過,而且很長的一段時間被當做廉價勞工。
7、8 年前接觸到的 Java 應用系統,一個是自己主導的、一個是有參與的、一個是維運而沒有權力介入的,還有現在維運的系統;都看到了一個很平常的問題:系統慢、很吃記憶體...等。而這些系統,甚至與其他見過的 80% 的應用系統,都有一個(很典型的)共同點:處理(關聯式)資料庫的資料。
除了自己主導做的系統很認真的設計外,餘者多讓我覺得:難怪很多人認為程式設計是廉價的。因為,連基本的資料庫處理方法都做的不好。
在這裡想指出兩個觀點:
- 資料連線的建立是耗時、不經濟的
- 除非是跨資料庫,否則少有同時多個資料庫連線的需要
話說回來,這個年代所做的已不是十多年前的 standalone program,而多半是 web application。使用的是 App Server 來做為程式的執行環境。這些 App Server 執行環境存在的主要目的,其中一個就是資源管理(/控),而且資料庫連線資源是大宗。程式基本上是向 DataSource (也就是 connection pool) 取得資料庫連線資源 (connection object),基於並受限於 pool 的管制以免資料庫 (DB server) 受到太大的網路連線需求衝擊。
然而,除非你買的是百萬元級 App Server(例如 WebLogic);否則,pool 中的資源並沒受制於 API 的規定而受特別控制;一旦,程式要求 connection 時,pool manager 就會試著產生一個 connection 給程式用:
// DataSource.getConnection() Connection conn = someDataSource.getConnection(); // ... return conn;
但抱歉的是,用的 App Server 不是百萬元起跳,程式也不只是一個 class 、一個 data table 來源,就可以把前端 (client side) 的作業需求處理完畢。一旦,一群 class、一堆 data table 的處理,各 class 程式中需要 connection 時,透過上面的 API 取用;那麼,一個作業 (one thread) 可能會用到很多 connection objects,而同時間不同的使用者作業(multi-thread),將勢必造成 connection 不足 (leak) 的現象。
索性,O.O. 的世界有個重要的觀念:封裝。我們可以做一個 helper class,封裝 get connection 的需求:
// ... Connection conn = ConnectionHelper.getConnection(dataSourceName); // ...
在其中有兩件事情要處理:
- 以 dataSourceName 向 context 取得 DataSource。
- 向 DataSource 取得 Connection。
第 1 點的做法:
- 使用 Servlet 的 load-on-startup 定義,對整個系統會用到的 DataSource 進行登記,以便爾後繼續取用。
- 若改用 Spring framework 的話,則 bean object 除了不為 lazy initialized 外,利用 init-method 來進行 DataSource 登記,而這個 DataSource 則用 JndiObjectFactoryBean 以 JNDI name 取得並注入 (inject) 即可。
- 若不是這兩個方法,而想自己做 singleton object(甚至用 static class) 的話,要注意的是:不要每次都呼叫/調用 new InitialContext() 及 lookup()。因為...它不經濟而且有點慢;現況來說,太常見到這類程式臨時在這裡採用這種方式的設計,做的只是上面的事。是浪費記憶體、[笨的可以] 的方法。可以做的是改用 static holder 技巧,該 holder 則是用 static inner class 在 class loading 初始化階段產生,用來 hold 住 DataSource 物件。
而這裡要做的是,以 dataSourceName 來對應以上面各方法取得的 DataSource 而已。
第 2 點的做法:
- return connection object 前,用 ThreadLocal 留住向 DataSource 取得的 connection object,不用每次都向 DataSource 要,逕自 ThreadLocal 物件取得即可,直到 thread 結束。
- thread,這裡多半指的是 HTTP thread。即然使用 ThreadLocal 物件來 "hold" 住及釋放資源,那麼必須找出 transaction 作業的起點,來進行取得與釋放動作;一般來說,實作的方法多半是 Servlet filter 或 Spring framework 的 OncePerRequestFilter,要注意的是該 filter "通常"是第一個(/最後一個)。
如果有必要的話,花一點力氣去偵測取得的 connection object 是否沒做最後的 close(),幫忙做一下 close(),以免 connection leak 發生;必竟用的不見得是百萬元級的 App Server 啊。
以上出現很多名詞與技巧,若不清楚、不懂、不知道,那表示這些東西一點兒都不廉價。
別再說:我也做過軟體、這不算什麼、[(一點也不難)找個會寫 Java 的人來做]...這種 "廉價" 的想法。 <除非他真的懂 Java>
ps. 這裡尚未探討到 XA / 2PC 的議題