2008年10月14日 星期二

static 的使用時機

首先大致了解一下 static 這個修飾字,及使用規則。

static 是屬於類別層級的修飾字,在記憶體裡面只存有一份,不論我們 new 了幾個物件,該成員或函示始終指向同一個記憶體位置。而 static 成員只能被 static 成員呼叫或存取。

在使用的時機方面,static 是在任何物件運作之前就會被呼叫的,因此我們也常常使用
main method 來作測試,再來就是常見的公用 method,類似 Utilites,與不會改變的常數等等。

下列有個例子舉的很好,可以拿來說明:

public class StaticModifier
{
int counter1 = 0;
static int counter2 = 0;

public void Increase(String s)
{
counter1++;
counter2++;
System.out.print(s + "'s counter1 = " + counter1);
System.out.println("; counter2(static) = " + counter2);
}

public static void main(String argv[])
{
StaticModifier sta1 = new StaticModifier();
StaticModifier sta2 = new StaticModifier();

sta1.Increase("sta1");
sta1.Increase("sta1");
sta2.Increase("sta2");
}
}
程式輸出為:
sta1's counter1 = 1; counter2(static) = 1
sta1's counter1 = 2; counter2(static) = 2
sta2's counter1 = 1; counter2(static) = 3
Reference:
http://www.javaworld.com.tw/jute/

2008年10月12日 星期日

Override equals(), override hashCode()

這項規則可以參考 java.lang.Object 的規格書

1. 在同一個應用程式執行期間, 對同一個物件呼叫 hashCode(), 必須回傳相同的整數結果
2. 如果兩個物件被 equals(Object) 視為相等, 則這兩個物件呼叫 hashCode() 必須獲得相同的整數結果
3. 如果兩個物件被 equals(Object) 視為不相等, 則這兩個物件呼叫 hashCode() 不必獲得相同的整數結果

若要方便產生這些原始碼,也可以參閱 commons.lang.builder
org.apache.commons.lang.builder

另外我們可以研究一下 java.lang.Object, java.lang.String 底下的原始碼
java.lang.Object

public boolean equals(Object obj) {
return (this == obj);
}
java.lang.String
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
很明顯的,equals()在比較是否 reference 到同一個記憶體位置,若為 String 物件,則比對物件的內容。

2008年10月7日 星期二

利用 JAD 將程式碼反組譯心得

最近有一項 task 還蠻有趣的,由於 Production 上的 JAR 檔案與版本控制上的程式碼不盡相同,因此每次改程式的方式,都是從測試機將 JAR 檔抓下來再將修好的 .class 檔案放進去測試,但這樣比較有風險,因為我們不知道放進去的程式會不會影響到其它的 binary 檔案。

為了作調整,將 Production 與 SVN 裡面的程式碼作同步是勢在必行了,但是 binary 檔案與 java 的格式本來就不同,那要如何作比較? 這裡提供一個簡單的方法,利用 jad.exe 將我們從 SVN checkout 的檔案反組譯,這樣他跟 JAR 檔就有比較的基準了。

每次 build 的檔案是從版本控制上下來的程式碼,比較容易追朔、也降低了風險,最重要的是我們可以確定測試機、與正式機的內容是一樣的。

2008年10月2日 星期四

開發產品軟體雜感

由於在軟體界已逾四年,碰過比較有印象的產品有ATG(電子商務平台),跟目前要開發的產品Firewire CMS(內容管理系統)。ATG是一個滿成熟的平台,在四五年前,就已經有controller-->manager-->類似hibernate OR mapping 的觀念,並擁有自己的 taglib,也就是整套都在他自己的環境下被封裝好了,需要照著產品自訂的流程跟API來作開發。

最近碰的這套是內容管理系統,以開發的角度來說是較容易些。因為他的 development guide 只定義了自己的API跟 taglib,除了要懂這些標籤跟少數的API,其實就沒有什麼太多技術面的問題,剩下的都是比較屬於如何利用他自己的流程、介面來管理內容。

而台灣自製的軟體產品我大概碰過兩套,都是跟知識管理有關係的,其中一套設計的比較好、利用struts,EJB來建構,裡面也有很多的design pattern可以參考跟學習,當然如果不作分散式的處理,使用EJB實在有點大材小用,也增加開發的額外負擔。

當我們的專案碰到產品時,應該不能以寫程式的角度來看,不然我們可能會認為,這些功能我用struts, spring 都作的出來,為什麼要增加額外的負擔再去學這些流程。而這些流程跟技術,都有一定的道理存在。

例如 CMS 強調的就是產品上稿,與產品之間的關連性,只要將這些element與關連建起來,利用他自製的tag,就可以很容易的將這些功能表現出來。

雖然我們能夠在這既有的 framework 上發揮的點並不多,但是這些產品畢竟都還是 java-based的,可以從開發的流程去檢討、可以試著整進CI、作適度的 refactoring,都可以有助於專案的品質。

2008年9月22日 星期一

java 的記憶體配置

了解 java 記憶體的配置,有助於程式撰寫的 performance tuning

首先以變數的角度來說明:
1. heap variable:
class variable(method 之外並加上 static 的修飾字)、instance variable、array component。
這類變數會自動被 JVM 初始化成預設值。

2. stack variable:
通稱廣義的區域變數,其佔用的記憶體在 stack 中,這類變數包括了狹義的區域變數(宣告在 method 內的變數)、
method parameter、exception-handler parameter、constructor parameter。此類變數會被 JVM 初始化成傳入值。
而狹義的區域變數不會被 JVM 初始化成預設值,使用者必須自行初始化該變數。

下列是以記憶體的配置來探討:
1. heap (dynamic memory allocate):
是動態配置記憶體空間,且可以隨時配置,由JVM負責。
只要是用new 產生的oject都是存放在heap中,且通常用一個存放在stack的reference 來指向這個oject
compiler 不會知道這個從heap中獲得多少空間,也不會知道其配置到的空間會存放多久。

2. stack (stack allocation):
其 Life cycle 會受到block scope的影響,當進入block時,會自動配置記憶體空間,離開這個SCOPE時,其記憶體配置就會隨之消失。
可以將object的reference 儲存於stack內。

Reference:
http://www.oreilly.com.tw/column_sleepless.php?id=j015
http://www.javaworld.com.tw/jute/

2007年12月18日 星期二

Creational 模式學習心得

Factory method: 適用於多型化的操作,利用一個 factory method,將封裝物件的行為,並將每個物件都對應於一個 factory class。

Builder: 適用於多型化的操作,builder method 用來組合物件的行為。

Prototype: 主要用來複製物件。

Singleton: 適用於只需要一份 instance 時(只能 new 出一份 instance)。

使用 Pattern除了能夠封裝細節,還能使程式更佳彈性。

2007年11月20日 星期二

PostgreSQL Hibernate 連線設定

以下的連線設定以 hibernate 3.0 以上版本為基礎, 除了基本的 hibernate 程式庫, 您還需要下載 PostgreSQL Driver, 才能順利執行.
由於 PostgreSQL 的資料型態與一般的 DB 略為不同, 建議在測試專案時可以將 "hbm2ddl.auto" 這個屬性設為 "true", 讓 hibernate 自動去對應最合適的資料型別, 並自動建立索引, sequence 等服務.

<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.postgresql.Driver</property>
<property name="connection.url">jdbc:postgresql://localhost:5432/postgres</property>
<property name="connection.username">inqgen</property>
<property name="connection.password">inqgen</property>

<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>

<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>

<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>

<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>

<mapping resource="inqgen/Person.hbm.xml"/>

</session-factory>

</hibernate-configuration>

PostgreSQL 資源簡介

PostgreSQL 為完全免費的資料庫, 並且支援友善的client 介面,
下面列了一些簡單的資源提供各位參閱.

Client Tools

pgAdmin III
該工具為在 8.0 以後的版本為內建,不需要另外安裝
http://www.pgadmin.org/

Aqua Data Studio
http://www.aquafold.com/downloads.html

Development Reference

國際中文版社群
http://postgresql.oss.tw/
PostgreSQL 8.2.5 Documentation
http://www.postgresql.org/docs/8.2/interactive/index.html

PostgreSQL 8.0.0 中文文件
http://twpug.net/docs/postgresql-doc-8.0-zh_TW/

PostgreSQL 資料型態
http://twpug.net/docs/postgresql-doc-8.0-zh_TW/datatype.html

PostgreSQL安裝及基本備份操作
http://blog.roodo.com/shihty/archives/2364269.html

PostgreSQL 備份筆記
http://www.javaworld.com.tw/roller/page/ingramchen?entry=2007_5_25_SimplePostgreSQLBackup

Using the PostgreSQL Driver
http://www.faqs.org/docs/ppbook/x20856.htm

PostgreSQL Driver
http://jdbc.postgresql.org/download.html#current

2007年11月14日 星期三

[轉貼]BAT 批次檔的備份方式

在此分享一個利用Bat批次檔備份資料的方式,包含檔案的複製、刪除、FTP下載與壓縮,也可以利用LOG檔記錄執行的內容。

BACKUP.BAT

rem ----- 檔案名稱 : BACKUP.BAT -------------------------------------------------------
rem ----- 用途 : 利用 XCOPY 和 .BAT 功能做備份 ----------------------------------------

rem ----- 將 BACKUP.BAT ?#91;入 系統排?#123; ( ?#93;可以手動?#91;入 [?#125;始][?#123;式集][啟動] 中 ) ------
rem at 23:00 /every:s,m,t,w,th,f,sa "d:\LU\BACKUP.bat"

rem ----- XCOPY 用法 可用 "XCOPY /?" 查詢 ---------------------------------------------
rem XCOPY 差異備份常用參數
rem /D:m-d-y 複製在指定日期當天或之後發生變更的檔案。如果沒有給日期,只複製那些來源檔案日期比目的檔案日期為新的檔案。
rem /H 時複製隱藏檔和系統檔。
rem /I 如果目的不存在且複製一個以上的檔案的話,就假?#93;指定的目的一定是目錄。
rem /S 複製每個目錄及其?#93;含的子目錄,不複製空目錄。
rem /E 複製每個目錄及其?#93;含的子目錄,?#93;複製空目錄。/S 與 /E 相同,能夠用來修改 /T。
rem /Y 不要提示您確?#123;是否要覆蓋一個已經存在的檔案。

rem ----- 增?#91; route 資料, 廢除 ---------------
rem route add 140.127.177.17 192.168.0.2

rem ----- 將?#125;始備份時間寫入 log 紀錄中 -----------------------------------------------------------------------
rem %date% 表示日期環境變數, %date:~0,4% 表示 日期環境變數中從第 0 個位置抓出 4 個字
echo %date:~0,4%%date:~5,2%%date:~8,2% 網頁資料備份紀錄檔 > D:\Backup\log\%date:~0,4%%date:~5,2%%date:~8,2%.log
echo 始備份時間 >> D:\Backup\log\%date:~0,4%%date:~5,2%%date:~8,2%.log
TIME /T >> D:\Backup\log\%date:~0,4%%date:~5,2%%date:~8,2%.log

rem ------ [完整備份] 將網站資料備份到 以 日期為目錄名稱 的目錄中 --------------------------------------------------------
rem xcopy c:\inetpub D:\Backup\%date:~0,4%%date:~5,2%%date:~8,2% /h /i /s /k /o /y >> D:\Backup\log\%date:~0,4%%date:~5,2%%date:~8,2%.log

rem ----- [差異備份] 備份 MSN 表情符號, 改名成 .gif 方便?#91;看, 更名失敗表示重複, 可刪除
XCOPY "C:\Documents and Settings\lu\Application Data\Microsoft\MSN Messenger\1061698994\CustomEmoticons" "D:\CustomEmoticons" /D /H /I /S /Y
REN D:\CustomEmoticons\*.dat *.gif
DEL D:\CustomEmoticons\*.dat

rem ------ [差異備份] 備份 MSN 表情符號 到 File Server 的個人備份區
XCOPY D:\CustomEmoticons Z:\CustomEmoticons\ /D /H /I /S /Y

rem ------ [差異備份] 備份 我的最愛 到自己的 D: 磁碟 --------------------------------------------------------------
rem 使用 %USERPROFILE% 環境變數, 它代表 "C:\Documents and Settings\(使用者登入帳號)"
XCOPY %USERPROFILE%\Favorites "z:\Favorites\" /D /H /I /S /Y
rem XCOPY "C:\Documents and Settings\lu\Favorites" "D:\Favorites\" /D /H /I /S /Y

rem ------ [差異備份] 備份 Outlook Express 6.x 通訊錄 到自己的 D: 磁碟 -------------------------------------------------------------------
XCOPY "%USERPROFILE%\Application Data\Microsoft\Address Book" "D:\Address Book\" /D /H /I /S /Y
rem XCOPY "C:\Documents and Settings\lu\Application Data\Microsoft\Address Book" "D:\Address Book\" /D /H /I /S /Y

rem ------ [差異備份] 備份 我的最愛 到 File Server 的個人備份區 -----------------------------------------------------------
XCOPY %USERPROFILE%\Favorites "z:\Favorites\" /D /H /I /S /Y
rem XCOPY "C:\Documents and Settings\lu\Favorites" "z:\Favorites\" /D /H /I /S /Y

rem ------ [差異備份] 備份 Outlook Express 6.x 通訊錄 到 File Server 的個人備份區 -----------------------------------------------------------
XCOPY "%USERPROFILE%\Application Data\Microsoft\Address Book" "z:\Address Book\" /D /H /I /S /Y
rem XCOPY "C:\Documents and Settings\lu\Application Data\Microsoft\Address Book" "z:\Address Book\" /D /H /I /S /Y

rem ----- [壓縮完整備份] 將 D:\lu 用 WinRAR 壓縮放在 d:\lu.rar 或 d:\日期.rar ----------------------
rem WinRar 是商業軟體, 網?#125; http://www.rarsoft.com/
rem c:\progra~1\winrar\rar a -r d:\lu.rar d:\lu\*
rem c:\progra~1\winrar\rar a -r d:\%date:~0,4%%date:~5,2%%date:~8,2%.rar d:\lu\*
rem "C:\Program Files\WinRar\rar a -r d:\%date:~0,4%%date:~5,2%%date:~8,2%.rar d:\lu\*

rem ----- [壓縮完整備份] 將 D:\lu 用 7-zip 壓縮 ----------------------
rem 7-Zip 是免費軟體, 網?#125; http://www.7-zip.org/
rem "C:\program Files\7-zip\7z" a -r c:\%date:~0,4%%date:~5,2%%date:~8,2%.7z d:\lu\*.*
rem 壓縮成為 .zip 格式
rem "C:\program Files\7-zip\7z" a -r -tzip c:\%date:~0,4%%date:~5,2%%date:~8,2%.zip d:\lu\*.*

rem ----- 差異備份, 異動檔案輸出到 1.log -----
rem XCOPY D:\LU Z:\LU\ /D /H /I /S /Y >> c:\1.log
XCOPY D:\LU Z:\LU\ /D /H /I /S /Y

rem ------ 備份 Outlook 信件 -------------------------------------------------
rem echo 請先關閉 Outlook , 否則 Outlook 信件無法備份
rem pause
XCOPY "D:\Outlook" "z:\Outlook\" /D /H /I /S /Y

rem ------ 備份 hosts 檔案 -------------------------------------------------
XCOPY C:\WINDOWS\system32\drivers\etc\hosts z:\ /D /H /I /S /Y

rem ------ 輸出備份完成時間 -----------------------------------------------
echo 結束備份時間 >> D:\Backup\log\%date:~0,4%%date:~5,2%%date:~8,2%.log
TIME /T >> D:\Backup\log\%date:~0,4%%date:~5,2%%date:~8,2%.log
FTPSCEIPT.bat
FTP -s:ftp.txt 201.80.100.182
ftp.txt
inqgendoh  //使用者名稱
inqgen
//密碼
cd backup //切換目錄
bin
prompt
mget *.* //取得檔案
bye


參考來源:資安論壇

2007年9月12日 星期三

Collections API 簡介

Collections 提供了很多好用的method讓您操作資料結構
以下用一個簡單的範例來介紹:

double array[] = {123, 112, 57, 355, 255};
double subArr[] = {57};

List<Double> list = new ArrayList();
List subList = new ArrayList();

for (int i = 0; i < array.length; i++) {
list.add(new Double(array[i]));
}

for (int j = 0; j < subArr.length; j++) {
li.add(new Double(subArr[j]));
}

//排序, 印出 57,112,123,255,355
Collections.sort(list);

//反向排序
Collections.sort(list, Collections.reverseOrder());

//亂數排序
Collections.shuffle(list);

//倒轉元素, 印出 255,355,57,112,123
Collections.reverse(list);

//移動元素, 正數元素會往右移動, 負數則往左移動
Collections.rotate(list, 1);

//元素出現的頻率, 該例回傳 1
Collections.frequency(list, new Double(112));

//搜尋該元素的位置, 但使用該 method 必須是先排序過的
//例如使用Collections.sort(), 不然會回傳負值, 本例印出 1
Collections.binarySearch(list, new Double(112));

//將新元素取代原來的物件內容, 該例會全部印出 100
Collections.fill(list, new Double(100));

//比較子元素最後的位置, 本例印出 2(若找不到回傳-1)
Collections.lastIndexOfSubList(list,subList)

//比較子元素的位置
Collections.indexOfSubList(list,subList)

JOIN 的簡易操作

要連接兩張資料表, JOIN 是不可或缺的指令, 以下用兩張很簡單的表格,
來簡介如何操作 JOIN.

Table1: test1

Table1: test2

id

name

id

name

1

Allen

1

Allen

2

Bill

2

Bill

5

Jack

4

Jack


若我們的SQL指令如下:

select *
from test1 as t1 INNER JOIN test2 as t2
on t1.id = t2.id

則表示兩張表格的權重相等, 必須兩張表格都有相同 id 的資料, 才會取出
(在本例中, 兩張表格都有 id 1,2 這幾筆資料已達到 inner join 的條件)

再看下一條指令:

select *
from test1 as t1 LEFT JOIN test2 as t2
on t1.id = t2.id

使用 left join 表示合併的權重以左邊的表格為主, 條件會以左邊表格的 id 去比對
在本例中, 會列出 id 值為 5 的record.

至於 RIGHT JOIN 的原理跟 LEFT JOIN 是完全相同的,
只是差在它是以右邊表格的權重為主.

2007年9月10日 星期一

指定編譯器版本(eclipse, IDEA)

在進行不同的專案時,有時會遇到專案的編譯版本是不同的,
若沒有加以指定、則會遇到版本錯誤的訊息;以下簡易的分享eclipse、IDEA的指定方式。

eclipse: project-->properties-->java complier-->complier compliance level

idea: project-->module settings-->source-->language level

只要設定完成、加以重新編譯,專案就很容易可以切換到不同版本的JDK了。

2007年9月5日 星期三

Composing Methods

1. Extract Method
動機: 函示過於冗長, 或一眼無法得知該函示的用意
方法: 將程式碼組織, 並獨立出來, 注意在 refactoring 的過程, 需取有意義的函示與變數名稱

2. Inline Method
動機: 若該函示屬於不必要的, 或多餘的
方法: 將函示的程式碼整合進來, 並移除該函示

3. Inline Temp
動機: 暫時變數只被賦值一次
方法: 去除暫時變數, 直接使用運算結果, 可以在變數前加上 final 檢驗之

4. Replace Temp with Query
動機: 暫時變數只被賦值一次, 但可能被多次運用
方法: 去除暫時變數, 將運算的內容提煉到一個 private method

5. Introduce Explaining Variable
動機: 運算式複雜又難以閱讀時
方法: 將運算的內容提煉成變數, 來解釋該內容

6. Slipt Temporary Variable
動機: 程式有些變數不只被被賦值一次
方法: 針對每次賦值, 提供一個獨立、暫時的變數

7. Remove Assignments Parameters
動機: 有參數被直接賦值, 會降低程式的清晰度
方法: 以一個暫時變數, 代替參數的位置

8. Replace Method with Method Object
動機: 有一個大型函示, 無法直接對區域變數採行 Extract Method, 或有很多的區域變數是可以重複利用的方法: 將函示放進一獨立物件, 並將這些區域變數變成物件的 field

9. Substitute Algorithm
動機: 想把某個演算法替換為更清晰的演算法
方法: 將函示本體的演算法做替換

2007年8月28日 星期二

簡易的效能測試方式

以下是一支簡易的 method,可以讓您了解程式的運作花了多少資源, 以便調整.

並介紹 Runtime.getRuntime().maxMemory(), Runtime.getRuntime().totalMemory()
Runtime.getRuntime().freeMemory() 是如何被使用的.
public class FreeMemoryTest {
public static void main(String[] args) {
//JVM 能從OS得到最大的記憶體,如果下了 -Xmx 參數,將以該參數為主
long maxMem = Runtime.getRuntime().maxMemory();
//JVM 已經從OS得到可使用的記憶體,會越取越多,直到 maxMemory 的最大容量,超過則會丟出 java.lang.OutOfMemoryError
long totalMem = Runtime.getRuntime().totalMemory();
//目前使用完剩下的記憶體
long startMem = Runtime.getRuntime().freeMemory();
long startTime = System.currentTimeMillis();

for (int i = 0; i <= 10000; i++) {
String str = "foo";
System.out.println(str);
}
long endMem = Runtime.getRuntime().freeMemory();
System.err.println("使用記憶體(Bytes): " + (startMem - endMem));
long endTime = System.currentTimeMillis();
System.err.println("使用時間(毫秒): " + (endTime - startTime));
System.err.println("總共記憶體(Bytes):" + totalMem);
System.err.println("目前最大記憶體(Bytes):" + maxMem);
}
}

詳細可再參考
透過 Java 參數來改善 Java 效能

2007年8月26日 星期日

分頁效能的實作

Displaytag 有一個常見的 issue, 就是在撈出大量資料時, 會出現 Outofmemory 的錯誤.
因為 displaytag 的預設值, 是將所有的資料一次撈到記憶體, 再執行分頁的動作, 然而這樣的作法是相當消耗記憶體的, 也容易引起exception.
在 displaytag 1.1 版本中(若您的版本是1.0, 請記得切換至1.1), 我們只需要幾個簡單的設定, 就可以改善這個狀況.

先看下列的設定:

<display:table id="row" name="RESULT_LIST" partialList="true" requestURI=""
class="list" pagesize="10" size="${RESULT_SIZE}">

基本上跟 display tag1.0 無太大差異, 但是請記得將 partialList="true" 加入, 表示 displaytag 開啟了有限制的分頁機制,
而 pagesize 為每頁的 record 數目, size 則為資料的總筆數. 該機制與以前最大的不同, 就是只抓取 pagesize 所指定的數量
(該例為10筆, 也就是一次只取出十個物件).

為了確切的達成分頁功能, 我們還需要幾個實作,
先看 Controller 的部分:
...
int pageIndex = getPageIndex(request, getPageIndexName("row"));
//利用 displaytag 的屬性 id="row", 取得目前在第幾頁

int resultSize = dao.getAllCount();
//總共的筆數

List list = dao.search(pageIndex, pageSize);
//目前的筆數

....
request.setAttribute("RESULT_SIZE", new Integer(resultSize));
request.setAttribute("RESULT_LIST", list);

在這個例子當中, 要注意的是 pageIndex 利用到兩個 mtehod getPageIndexName(),
getPageIndex(), 其用意即註解所示, 取得當前的頁數, 詳細實作如下:
....

import org.apache.commons.validator.GenericValidator;
import org.displaytag.util.ParamEncoder;
import org.displaytag.tags.TableTagParameters;

public static String getPageIndexName(String encoder) {
String pageIndexName = new ParamEncoder(encoder).
encodeParameterName(TableTagParameters.PARAMETER_PAGE);
return pageIndexName;
}

public static int getPageIndex(HttpServletRequest request, String pageIndexName) {
int pageIndex = GenericValidator.isBlankOrNull(request.getParameter(pageIndexName)) ? 0
:(Integer.parseInt(request.getParameter(pageIndexName)) - 1 );
return pageIndex;
}
....

而在 dao.getAllCount(), dao.search(), 這兩個呼叫當中, 是為了分別取得總筆數及目前的筆數,
當然 DAO 層的實作可能依照您的 persistence, 而有所不同.

若 dao.search() 是利用 Hibernate 實作的, 範例大致如下:
....
public List search(int pageIndex, int pageSize){
List values;

String hql = "from Person";
Query query = getSession().createQuery(hql);

if (pageIndex >= 0) {
query.setFirstResult(pageSize * pageIndex);
}

if (pageSize > 0) {
query.setMaxResults(pageSize);
}

values = query.list();

return values;
}
....

2007年8月21日 星期二

Session Level cache

Hibernate 的 session 物件有一種 cache 機制, 可維護一份您存取資料後的 instance.
因為存取資料庫的連線, 需要不小的開銷. 該機制將存取 persistence object 的結果讀進記憶體(利用一份map物件來維護), 若在 session 結束後, 有相同的SQL操作, 則 cache 機制可以直接將物件回傳, 不用再執行額外的資料庫連線動作; 而這種存活於 session 內的 cache 機制, 又稱為 session Level cache.

用一簡例說明:

....
session.beginTransaction();

Person aPerson = (Person) session.load(Person.class, personId);
aPerson.getAge();

Person aPerson2 = (Person) session.load(Person.class, personId);
aPerson2.getAge();

session.getTransaction().commit();
session.close();
....

您可以觀察 console 內SQL的執行:
Hibernate: select person0_.PERSON_ID as
PERSON1_2_0_, person0_.age as age2_0_, person0_.firstname as
firstname2_0_, person0_.lastname as lastname2_0_ from PERSON person0_
where person0_.PERSON_ID=?
我們可以發現, 雖然 session.load() 呼叫了兩次, 但由於 cache 機制,
會直接回傳記憶體中的物件給 aPerson2 存取, 如此便減少了一次資料庫的連線成本

但有時存取過於頻繁, cache 內有太多的物件有可能會導致 OutOfMemory,
若您想釋放 cache 中的記憶體, 可以使用 session.clear(), session.evict(object),
另外 session.close() 也會結束該段 cache.

以上例而言:
...
Person aPerson = (Person) session.load(Person.class, personId);
aPerson.getAge();

session.evict(aPerson);
//session.clear()

Person aPerson2 = (Person) session.load(Person.class, personId);
aPerson2.getAge();
...
您會發現 console 會執行兩段SQL指令, 因為 session 中的 cache 已被清除了,session.load()
找不到對應的資料, 就會再重起一段資料連線.

Reference:
Hibernate Gossip: 簡介快取(Session Level)
Reference Documentation

2007年8月20日 星期一

JavaScript 開發筆記 - 格式驗證

格式驗證是常見的議題了, 這裡分享幾種簡單的格式驗證:

郵件格式驗證

function verifyMail(mail) {
var filePath1 = document.all(mail);

if (filePath1.value.length == 0) {
window.alert('請輸入信箱');
return false;
}

apos = filePath1.value.indexOf("@")
dotpos = filePath1.value.lastIndexOf(".")

if (apos<1||dotpos-apos<2) {
window.alert('請輸入正確的郵件格式');
return false
}

return true;
}

2007年8月16日 星期四

Implicit objects 的顯示方式

一般我們在 displaytag 顯示 List 物件時, 會使用下列的方式:


< id="row" name="${queryHospital}" requesturi="" class="sample" pagesize="">" >

< titlekey="text.id" sortable="true">
< href="hospitalList.do?METHOD=editHospital&id=< c:out value=">" >
< value="${row.id}">< value="${row.id}">< /a >
< /display:column >
......
< /display:table >


也就是將物件以 EL 的方式接收, 並利用 display:table 中的屬性 "id", 來作轉換,如此就可以很容易的在
<> 標籤裡,利用屬性 id 所設定的值, 將List物件的property一一取出.

但由於各家伺服器的Web Container所支援的方式不盡相同, 導致在 displaytag 的 "id" 屬性失效, 如此一來 id 完全無法帶出 property 中的資料.

若您的需求僅是將資料作動態的連結, displaytag 還是有幾個方便好用的屬性,
可以解決該問題.

引用一下上面這段:

< titlekey="text.id" sortable="true">
< href="hospitalList.do?METHOD=editHospital&id=< c:out value=">" >< value="${row.id}">< /a >
< /display:column >

可以簡化成:

< property="id" href="hospitalList.do?METHOD=editHospital" titlekey="text.id" paramid="id" paramproperty="id" sortable="true">

該屬性很簡單明瞭, 尤其 paramProperty 可以讓我們動態的去呼叫 Property, 而使用 href, paramId, paramProperty 也有某種程度達到簡化的目的.

該方法在 weblogic 9.2 是測試通過的, 但是displaytag 從去年的六月就停止開發了, 若有相關的 bug, 可能需要自己動修改.
若您的頁面需求比較複雜, 也可以考慮別種solution來實作.

#後記:該問題又找到了一個解決方案,將 display:table 內的ID屬性,更新為UID即可.

displaytag - Implicit objects created by table
http://displaytag.sourceforge.net/11/tut_implicitobjects.html
eXtremeComponents
http://code.google.com/p/extremetable/
jmesa
http://code.google.com/p/jmesa/

2007年8月12日 星期日

Date migration

我們在移動(或修改)資料表的時後, 常常會需要保留原本的資料內容,
因此資料整合的步驟就變的很重要.

其步驟大致為:

1. 建立 temp table
2. 從 original table 複製資料到 temp table
3. 刪除 original table, 並建立新的 schema
4. 從 temp table 複製資料至 新建的 table
5. 刪除 temp table

其中比較關鍵的SQL, 就是複製資料表中的資料內容, 這裡以一個 Person 的表格為例
如下所示:

CREATE TABLE temp_person (
PERSON_ID bigint(20) NOT NULL auto_increment,
age int(11) default NULL,
firstname varchar(255) default NULL,
lastname varchar(255) default NULL,
PRIMARY KEY (`PERSON_ID`)
);

insert into temp_person(person_id, age, firstname, lastname)
select person_id, age, firstname, lastname
from person;

2007年8月10日 星期五

Collection Framework 概觀

Collection Framework 是實現資料結構的重要介面, 基本上如下表所示,
其中 Set, List 繼承於 Collection 介面.
Set --> StoredSet
Map --> StoredMap

Interface

Implementation

Historical

Set(禁止重複)

HashSet(未排序)



TreeSet
(
照字母排序)

LinkedHashSet
(按照加入的順序排序,
實現queue的先進先出)


List(可重複)


ArrayList
(
未排序)


LinkedList(特性同上)

Vector
Stack

(thread-safe)

Map(key-value)

HashMap(未排序)


TreeMap

(照字母排序)

LinkedHashMap
(
特性同上)

Hashtable
Properties

(thread-safe)


三個介面的特性已以大致在表中呈現, 其中 StoredSet, StoredMap
其實是利用 TreeSet, TreeMap 介面來實現. 下面舉一個簡例:
Comparator comparator = Collections.reverseOrder();
Set reverseSet = new TreeSet(comparator);
reverseSet.add("Bernadine");
reverseSet.add("Elizabeth");
reverseSet.add("Gene");
reverseSet.add("Clara");
System.out.println(reverseSet);
output: [Gene, Elizabeth, Clara, Bernadine]

該例呼叫 Collections.reverseOrder(), 再傳入一個 Comparator, 使得 TreeSet
具有倒序的效果, 然而 TreeSet 本身的特性是照字母排序的, 會先排序後, 再執行 Comparator
裡面的 method.

在 Linked 開頭的部分(LinkedHashSet, LinkedList, LinkedHashMap)
都實現了 queue 的資料結構, 也就是先進先出, 跟按照字母排序的 Tree..略有不同
一個簡例如下:
Set linkedHashSet = new LinkedHashSet();
linkedHashSet.add("Bernadine");
linkedHashSet.add("Elizabeth");
linkedHashSet.add("Will");
linkedHashSet.add("Gene");
linkedHashSet.add("Elizabeth");
linkedHashSet.add("Clara");
System.out.println(linkedHashSet);
output: [Bernadine, Elizabeth, Will, Gene, Clara]

那麼使用這些資料結構的時機, 若不需要排序的, 可以使用 Hash 的介面. 若有排序的需求,
可以利用 Tree, Linked..的介面, 但是速度會比較慢. 在 Thread-safe 方面, 則可以參考 Vector, Stack, Hashtable, Properties.

在最後補充遍巡(Iterator) Hash 的方式:
HashMap m = new HashMap();
m.put("foo", "foo");

Set entrySet = m.entrySet();
Iterator it = entrySet.iterator();

while(it.hasNext()){
Map.Entry me = (Map.Entry)it.next();
Object key = me.getKey();
Object value = me.getValue();
}
Reference:
Introduction to the Collections Framework