JWorld@TW the best professional Java site in Taiwan
      註冊 | 登入 | 全文檢索 | 排行榜  

» JWorld@TW » Java Tools  

按列印兼容模式列印這個話題 列印話題    把這個話題寄給朋友 寄給朋友   
reply to topicthreaded modego to previous topicgo to next topic
本主題所含的標籤
無標籤
作者 Hibernate + Struts 學習筆記 [精華]
annhy

來呦~



發文: 45
積分: 2
於 2003-10-01 18:18 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
Hibernate + Struts 學習筆記
作者: Annhy

寫在前面

最近開始學 Hibernate + Struts,大概是因為我資質駑鈍,
總覺得看的東西不算少(這要感謝各位前輩),但是寫起程式來就是不太順。

所以我想把一些心得與疑問貼在這裡,一則可以向各位請教,
再則也可以提供後來學習的人參考。

各位如果覺得我寫的有問題,還請一定要提出批評與指教,
有批評我才會進步,有指教我會進步得更快。

我會先從後端的 Hibernate 開始進行,然後再加上前端 Struts 的部分。
既然是沿路將相關的工作內容與疑問記下,可能會看起來有點混亂,
還請忍耐。希望我有恆心與毅力把它完成...

為了避免侵犯我們公司的智慧財產權,以及營業秘密等頭痛的問題,
我決定不使用目前公司所上線使用的系統當例子
(而且它的商業邏輯已經變得有點龐大,大到我不知道怎麼拿來當作教學文件了),
改用比較像是小玩具等級的程式當作範例程式,
之前的那個例子就讓它隨風而逝吧...

我參考的資料:
1. hibernate 快速入門
http://www.javaworld.com.tw/jute/post/view?bid=11&id=3291&sty=1&tpg=1&age=-1
2. Introduction to Hibernate from theserverside
http://www.theserverside.com/resources/articles/Hibernate/IntroductionToHibernate.pdf
3. Hibernate2 Reference Documentation
http://www.hibernate.org/hib_docs/reference/pdf/hibernate_reference.pdf

================================================================================

功能需求

我們要開發一個(非常陽春的)相片管理系統,它的功能非常的簡單。大致如下:

1. 提供對相片的 新增/修改/刪除/查詢 的功能。
2. 為了能夠比較方便的管理相片,我們要建立相片的分類 (也就是相簿),
  此分類為階層式的樹狀結構,也就是說 leaf node 為相簿,
  non-leaf node 為相簿之分類。
  (分類也要有 新增/修改/刪除/查詢 的功能)
3. 一個相簿中可以有多張相片,一張相片也可以同時歸類於不同的相簿中。

類別設計

根據剛剛所定義的需求,可以很快的定出兩個 domain class,那就是 Photo 與 Album。

1. Photo: 紀錄相片的相關資訊
1
2
3
4
5
6
7
Photo {
 id : Long;               // persistent 時所用的唯一識別碼
 fileName : String;       // 檔名
 title : String;          // 照片標題
 description : String;    // 照片說明
 albums : Set;            // 此照片所屬的相簿
}


Thinking 1:
 id 的資料型態用 Long,而不是 long,我看到的 Hibernate 範例幾乎都是這樣用的。
 這是因為 Long 可以有 null 值,用來判斷尚未被 Hibernate 存入資料庫的物件很方便。
 若用 long,0 或 -1 就可能比較容易會不小心與真正的值相衝突。

 另外,為什麼不用 Integer 呢?這我不知道,大概是怕 integer 的範圍不夠大,
 如果設計時沒考慮到,等到上線時爆掉,就很麻煩吧...

Thinking 2:
 因為 albums 中不應該出現重複的分類,所以用 Set 而不用其它的 Collection,
 可以避免發生不小心的情況。
 另外 albums 的排序,我想應該用分類的 id 來當作排序依據。

2. Album: 紀錄 Photo 的分類階層架構
1
2
3
4
5
6
7
8
Album {
 id : Long;             // persistent 時所用的唯一識別碼。
 title : String;        // 相簿標題
 description : String;  // 相簿說明
 photos : Set;          // 屬於此相簿之所有相片物件
 parent : Album;        // 上層相簿分類
 children : Set;        // 屬於此分類之下層相簿物件
}


Thinking 1:
 與 Photo.albums 相同的理由,所以用 Set。
 這是個雙向的 association,想不起來哪裡看過一個條款,
 它說如果雙向連結不好維護或效率很差,就把它改成單向的。
 不過既然 Hibernate 的範例是這樣寫,那我就先這樣用了,
 應該沒問題吧...

Thinking 2:
 因為整個相簿分類是一個樹狀結構,所以需要有 parent 與 children
 來記錄相互間的架構關係。

 這裡又扯出一個問題,那就是樹狀結構是否應該有一個共同的根?
 也就是說如果我有三大分類 [家人], [學生時代], [其它],
 我們是否應該為它們建立一個共同的父節點呢?

 我個人是比較傾向建立這樣的 (虛擬) 共同父節點 (就取為 "我的相簿" 好了),
 這樣寫程式時,可以減少許多判斷上的工作量。

我的相簿
  |
  +--家人
  |  |
  |  +--親愛的老婆大人
  |  |
  |  +--寶貝女兒
  |  
  +--學生時代
  |  |
  |  +--大學
  |  |
  |  +--研究所
  |  
  +--其它

================================================================================

類別實作

因為 Album 與 Photo 是 domain objects,所以放在 annhy.photo.domains 這個 package 之下。
根據之前的類別設計,我們可以很容易地把它們實作出來,但它們目前只有 private data member 與
getter/setter methods,是不折不扣的 Java Bean。

這樣還不夠,為了要使它們更好用,我們還要加上一些東西:

a. 提供額外的建構式。除了預設建構式以外,再加上傳入一個字串參數 (fileName) 的建構式。

b. 覆寫 Object 類別的 toString(), equals(), hashcode() 等函式。

c. 實作 Comparable 介面,以及 compareTo() 函式。

d. 套用 Encapsulate Collection (封裝群集) 的重構手法。
  (註: 在 Refactoring 一書的 p.208 有一個 Encapsulate Collection (封裝群集) 的手法。
  按照它的說法,我不應該為整個群集 (例如: Photo.albums) 提供 getter/setter,
  而應該提供用以為群集 add/remove 元素的函式。所以我為這兩個 class 加上相對應的函式,
  但暫不將原有 getter/setter 移除,以免 Hibernate 發生危險。)

e. 提供 finder methods,以便搜尋物件。因為 finder methods 與 Hibernate
  的藕合度較高,所以把它們抽離出來,移至 Albums.java 與 Photos.java。(晚點再寫)

Photo.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package annhy.photo.domains;
 
import java.util.*;
 
/**
 * 用來表示 Photo 資料的物件。
 *
 * @author Annhy
 * @version 1.0, 2003/10/01
 */
public class Photo implements Comparable {
  //================
  //== Constructors
  //================
  public Photo() {
  }
 
  // 為了方便起見,多加一個建構式
  public Photo(String fileName) {
    this.fileName = fileName;
  }
 
  //================
  //== data members
  //================
  private Long id;
  private String fileName;
  private String title;
  private String description;
  private Set albums = new TreeSet();  // 有順序,所以用 TreeSet
 
  //================================
  //== getter & setter methods
  //================================
  public Long getId() {
    return id;
  }
  public void setId(Long id) {
    this.id = id;
  }
 
  public String getFileName() {
    return fileName;
  }
  public void setFileName(String fileName) {
    this.fileName = fileName;
  }
 
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
 
  public String getDescription() {
    return description;
  }
  public void setDescription(String description) {
    this.description = description;
  }
 
  public Set getAlbums() {
    return albums;
  }
  public void setAlbums(Set albums) {
    this.albums = albums;
  }
 
  //====================
  //== override methods
  //====================
  /**
   * 覆寫 Object.toString()
   * @return debug 用的字串
   */
  public String toString() {
    return "[Photo(" + id + "): " + fileName+ "]";
  }
 
  /**
   * 覆寫 Object.equals()
   * @param other 欲比較的另一個物件
   * @return 兩物件是否相等
   */
  public boolean equals(Object other) {
    if (other == this) {
      return true;
    }
    if (!(other instanceof Photo)) {
      return false;
    }
    Photo that = (Photo) other;
 
    return this.id.equals(that.id);
  }
 
  /**
   * 覆寫 Object.hashCode(),這樣才能用於 hash 物件
   * @return 物件的 hash code
   */
  public int hashCode() {
    return this.id.hashCode();
  }
 
  /**
   * 實作 Comparable.compareTo()
   * @param other 欲比較的另一個物件
   * @return 比較大小的結果
   */
  public int compareTo(Object other) {
    Photo that = (Photo) other;
    return this.id.compareTo(that.id);
  }
 
  //====================================
  //== Encapsulate Collection (封裝群集)
  //====================================
  public void addAlbum(Album album) {
    albums.add(album);
 
    // 加入反向連結 (要先判斷!!)
    if (!album.getPhotos().contains(this)) {
      album.addPhoto(this);
    }
  }
 
  public void removeAlbum(Album album) {
    albums.remove(album);
 
    // 移除反向連結 (要先判斷!!)
    if (album.getPhotos().contains(this)) {
      album.removePhoto(this);
    }
  }
 
  public void clearAlbums() {
    // 記錄原來的 snapshot
    Album[] arrSnapshot = (Album[]) albums.toArray(new Album[0]);
    for (int i = 0; i < arrSnapshot.length; i++) {
      removeAlbum(arrSnapshot[i]);
    }
  }
}


Album.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package annhy.photo.domains;
 
import java.util.*;
 
/**
 * 用來表示相片分類 (相簿) 的物件。
 *
 * @author Annhy
 * @version 1.0, 2003/10/01
 */
public class Album implements Comparable {
  //================
  //== Constructors
  //================
  public Album() {
  }
 
  // 為了方便起見,多加一個建構式
  public Album(String title) {
    this.title = title;
  }
 
  //================
  //== data members
  //================
  private Long id;
  private String title;
  private String description;
  private Set photos = new TreeSet(); // 有順序,所以用 TreeSet
  private Album parent;
  private Set children = new TreeSet(); // 有順序,所以用 TreeSet
 
  //================================
  //== getter & setter methods
  //================================
  public Long getId() {
    return id;
  }
  public void setId(Long id) {
    this.id = id;
  }
 
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
 
  public String getDescription() {
    return description;
  }
  public void setDescription(String description) {
    this.description = description;
  }
 
  public Set getPhotos() {
    return photos;
  }
  public void setPhotos(Set photos) {
    this.photos = photos;
  }
 
  public Album getParent() {
    return parent;
  }
  public void setParent(Album parent) {
    this.parent = parent;
  }
 
  public Set getChildren() {
    return children;
  }
  public void setChildren(Set children) {
    this.children = children;
  }
 
  //====================
  //== override methods
  //====================
  /**
   * 覆寫 Object.toString()
   * @return debug 用的字串
   */
  public String toString() {
    return "[Album(" + id + "): " + title + "]";
  }
 
  /**
   * 覆寫 Object.equals()
   * @param other 欲比較的另一個物件
   * @return 兩物件是否相等
   */
  public boolean equals(Object other) {
    if (other == this) {
      return true;
    }
    if (!(other instanceof Album)) {
      return false;
    }
    Album that = (Album) other;
 
    return this.id.equals(that.id);
  }
 
  /**
   * 覆寫 Object.hashCode(),這樣才能用於 hash 物件
   * @return 物件的 hash code
   */
  public int hashCode() {
    return this.id.hashCode();
  }
 
  /**
   * 實作 Comparable.compareTo()
   * @param other 欲比較的另一個物件
   * @return 比較大小的結果
   */
  public int compareTo(Object other) {
    Album that = (Album) other;
    return this.id.compareTo(that.id);
  }
 
  //====================================
  //== Encapsulate Collection (封裝群集)
  //====================================
  public void addPhoto(Photo photo) {
    photos.add(photo);
 
    // 加入反向連結 (要先判斷!!)
    if (!photo.getAlbums().contains(this)) {
      photo.addAlbum(this);
    }
  }
 
  public void removePhoto(Photo photo) {
    photos.remove(photo);
 
    // 移除反向連結 (要先判斷!!)
    if (photo.getAlbums().contains(this)) {
      photo.removeAlbum(this);
    }
  }
 
  public void clearPhotos() {
    // 記錄原來的 snapshot
    Photo[] arrSnapshot = (Photo[]) photos.toArray(new Photo[0]);
    for (int i = 0; i < arrSnapshot.length; i++) {
      removePhoto(arrSnapshot[i]);
    }
  }
 
  public void addChild(Album child) {
    children.add(child);
 
    // 加入反向連結 (要先判斷!!)
    if (!this.equals(child.getParent())) {
      child.setParent(this);
    }
  }
 
  public void removeChild(Album child) {
    children.remove(child);
 
    // 移除反向連結 (要先判斷!!)
    if (this.equals(child.getParent())) {
      child.setParent(null);
    }
  }
 
  public void clearChildren() {
    // 記錄原來的 snapshot
    Album[] arrSnapshot = (Album[]) children.toArray(new Album[0]);
    for (int i = 0; i < arrSnapshot.length; i++) {
      removeChild(arrSnapshot[i]);
    }
  }
}


目前檔案列表:
/annhy/photo/domains/Photo.java
/annhy/photo/domains/Album.java

================================================================================

設定 Hibernate 相關檔案

class 初步實作好之後,就可以設定 Hibernate 相關的檔案了。
這裡有三個檔案要做: hibernate.cfg.xml, Photo.hbm.xml, Album.hbm.xml
(本來是使用 hibernate.properties,改用 hibernate.cfg.xml 可以有額外的功能,
感謝 browser 的指導)

hibernate.cfg.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="Big5"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
  <session-factory>
    <!-- session factory properties -->
    <property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
    <property name="connection.driver_class">org.gjt.mm.mysql.Driver</property>
    <property name="connection.url">jdbc:mysql://localhost/photo_db?useUnicode=true&characterEncoding=Big5</property>
    <property name="connection.username">photo_db</property>
    <property name="connection.password">photo_db</property>
 
    <!-- domain object 的對應檔案 -->
    <mapping resource="annhy/photo/domains/Photo.hbm.xml"/>
    <mapping resource="annhy/photo/domains/Album.hbm.xml"/>
  </session-factory>
</hibernate-configuration>


這裡要注意的是這個特殊的字串: ?useUnicode=true&characterEncoding=Big5 ,
聽說這是因為 MySQL 不支援 UniCode,如果不加上這些,存入中文資料就會有問題。
還有,因為這是 XML 格式的檔案,所以 & 要替換為 &amp;
(請自己自己換成半形!! 為了這個問題,浪費了我幾個小時...)

Photo.hbm.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="Big5"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" >
<hibernate-mapping>
  <class name="annhy.photo.domains.Photo" table="photo">
    <id name="id" column="photo_id">
      <generator class="native" />
    </id>
    <property name="fileName">
      <column name="fileName" sql-type="text" />
    </property>
    <property name="title">
      <column name="title" sql-type="text" />
    </property>
    <property name="description">
      <column name="description" sql-type="text" />
    </property>
    <!-- Photo 與 Album 的 n:n 對應關係 -->
    <set name="albums" table="rel_album_photo" lazy="false" sort="natural">
      <key column="photo_id" />
      <many-to-many class="annhy.photo.domains.Album" column="album_id" />
    </set>
  </class>
</hibernate-mapping>


Album.hbm.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="Big5"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" >
<hibernate-mapping>
  <class name="annhy.photo.domains.Album" table="album">
    <id name="id" column="album_id">
      <generator class="native" />
    </id>
    <property name="title">
      <column name="title" sql-type="text" />
    </property>
    <property name="description">
      <column name="description" sql-type="text" />
    </property>
    <!-- Photo 與 Album 的 n:n 對應關係 -->
    <set name="photos" table="rel_album_photo" inverse="true" lazy="false"
         sort="natural">
      <key column="album_id" />
      <many-to-many class="annhy.photo.domains.Photo" column="photo_id" />
    </set>
    <!-- Album 自己的樹狀階層架構 -->
    <!-- 指向 parent -->
    <many-to-one name="parent" column="parent_id"
                 class="annhy.photo.domains.Album" />
    <!-- 指向 children -->
    <set name="children" inverse="true" lazy="false" sort="natural">
      <key column="parent_id" />
      <one-to-many class="annhy.photo.domains.Album" />
    </set>
  </class>
</hibernate-mapping>


這裡要注意的是:
1. 當使用了雙向連結,其中一個 class 必須要設定 inverse="true"
才行。至於是設定哪一邊的 class,似乎沒啥影響。

2. 因為在 MySQL 中 java.lang.String 預設對應為 varchar(255),
這個 size 通常不夠用,所以要用下面這種寫法,強制它對應到 text。
(感謝 chrischang 的指導

1
2
3
    <property name="title">
      <column name="title" sql-type="text" />
    </property>


這個 Album.hbm.xml 展示了 many-to-many 的關係,以及樹狀階層架構。
這兩種特殊用法,之前我都找不到範例,只好自己 try。
這就是我 try 出來的結果,還不錯,很直覺。

Thinking 1:
 在設定對應關係的地方,我都有設定 lazy="false"
 (不使用 Lazy Initialization),而且不能設定 class 的 proxy 屬性。
 這是因為之前我設定過這兩個選項時,如果在關閉 session 之後,才去讀取 photo.albums
 或 albums.children 這些 collection 就會產生 error。

 我知道 Hibernate 就是這樣設計的,但我不懂,難道 session 用完可以不關閉嗎?
 (手冊上面這麼描述 Session: A single-threaded, short-lived object ....)
 如果是 Web AP,每一個 HTTP request 都算是獨立的動作,這樣難道不會有問題?
 在我還搞不懂之前,我還是先不要亂用好了,反正在資料量不多的情況下,頂多只是效率比較差罷了...

 有沒有善心人士可以指點我一下...

目前檔案列表:
/annhy/photo/domains/Photo.java
/annhy/photo/domains/Album.java
/hibernate.cfg.xml
/annhy/photo/domains/Photo.hbm.xml
/annhy/photo/domains/Album.hbm.xml

================================================================================

ThreadLocalSession.java, Photos.java, Albums.java

到這裡,已經寫好 domain class 與 Hibernate 相關檔案,我們已經有足夠的東西
來產生測試資料庫。

我要先提供一個用以取得 session 的公用類別,因為我有用到 web 的程式,
所以此公用類別必須是 thread-safe 的,於是我參考了
http://hibernate.bluemars.net/42.html
http://hibernate.bluemars.net/114.html
之後,使用 ThreadLocal 變數來解決。

ThreadLocalSession.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package annhy.photo.hibernate;
 
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
 
public class ThreadLocalSession {
  // Hibernate 的設定環境物件,由 xml mapping 文件產生
  private static Configuration config;
  // SessionFactory
  private static SessionFactory factory;
  // ThreadLocal 變數,用來存放 ThreadLocalSession 物件
  private static final ThreadLocal sessionContext = new ThreadLocal();
 
  static {
    init();
  }
 
  private static final void init() {
    try {
      config = new Configuration().configure();
      factory = config.buildSessionFactory();
    }
    catch (HibernateException ex) {
      ex.printStackTrace(System.err);
      config = null;
      factory = null;
    }
  }
 
  public static Session openSession() throws HibernateException {
    Session session = (Session) sessionContext.get();
    if (session == null) {
      session = factory.openSession();
      sessionContext.set(session);
    }
    return session;
  }
 
  public static void closeSession() throws HibernateException {
    Session session = (Session) sessionContext.get();
    sessionContext.set(null);
    if (session != null) {
      session.close();
    }
  }
 
  public static Configuration getConfig() {
    return config;
  }
 
  public static SessionFactory getFactory() {
    return factory;
  }
 
}


有了 ThreadLocalSession 之後,我就可以著手進行 Photos.java, Albums.java 了。

Photos.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package annhy.photo.domains;
 
import java.util.*;
 
import annhy.photo.hibernate.*;
import net.sf.hibernate.*;
 
/**
 * 用來處理 Photo 的相關 static 函式。
 *
 * @author Annhy
 * @version 1.0, 2003/10/01
 */
public class Photos {
  //==================
  //== finder methods
  //==================
  public static Photo findByPK(long id) throws HibernateException {
    return findByPK(new Long(id));
  }
 
  public static Photo findByPK(Long id) throws HibernateException {
    Session s = ThreadLocalSession.openSession();
    Photo result = (Photo) s.load(Photo.class, id);
    ThreadLocalSession.closeSession();
    return result;
  }
 
  public static Collection findAll() throws HibernateException {
    Session s = ThreadLocalSession.openSession();
    Collection result = s.find("from " + Photo.class.getName() + " photo order by photo.id");
    ThreadLocalSession.closeSession();
    return result;
  }
}


Albums.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package annhy.photo.domains;
 
import java.util.*;
 
import annhy.photo.hibernate.*;
import net.sf.hibernate.*;
 
/**
 * 用來處理 Album 的相關 static 函式。
 *
 * @author Annhy
 * @version 1.0, 2003/10/01
 */
public class Albums {
  //==================
  //== finder methods
  //==================
  public static Album findByPK(long id) throws HibernateException {
    return findByPK(new Long(id));
  }
 
  public static Album findByPK(Long id) throws HibernateException {
    Session s = ThreadLocalSession.openSession();
    Album result = (Album) s.load(Album.class, id);
    ThreadLocalSession.closeSession();
    return result;
  }
 
  public static Collection findAll() throws HibernateException {
    Session s = ThreadLocalSession.openSession();
    Collection result = s.find("from " + Album.class.getName());
    ThreadLocalSession.closeSession();
    return result;
  }
}


目前檔案列表:
/annhy/photo/domains/Album.java
/annhy/photo/domains/Albums.java
/annhy/photo/domains/Photo.java
/annhy/photo/domains/Photos.java
/annhy/photo/hibernate/ThreadLocalSession.java
/hibernate.properties
/annhy/photo/domains/Photo.hbm.xml
/annhy/photo/domains/Album.hbm.xml

================================================================================

利用 Hibernate 產生對應的資料庫

現在終於可以建立資料庫,並且新增測試資料了。

PhotoTester.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package annhy.photo;
 
import annhy.photo.domains.*;
import annhy.photo.hibernate.*;
import net.sf.hibernate.*;
import net.sf.hibernate.tool.hbm2ddl.*;
 
public class PhotoTester {
  public static void main(String[] args) throws HibernateException {
    // 產生 Database Schema Script 文件並建立資料庫
    generateDbSchemaScript(true);
 
    // 建立測試資料
    insertTestData();
 
    // 查詢測試資料
    assert Albums.findAll().size() == 8 : "應該有 8 個 Album";
    assert Photos.findAll().size() == 3 : "應該有 3 個 Photo";
    assert Photos.findByPK(1).getAlbums().size() == 1 : "Photo 1 歸屬於 1 個相簿";
    assert Photos.findByPK(3).getAlbums().size() == 2 : "Photo 3 歸屬於 2 個相簿";
  }
 
  public static void generateDbSchemaScript(boolean affectToDb) throws
      HibernateException {
    SchemaExport dbExport = new SchemaExport(ThreadLocalSession.getConfig());
    dbExport.setOutputFile("Photo_DB_Schema.sql");
    dbExport.create(false, affectToDb);
  }
 
  public static void insertTestData() throws HibernateException {
    Session s = ThreadLocalSession.openSession();
    Transaction t = s.beginTransaction();
 
    // create all catalog objects
    Album[] c = new Album[8];
    c[0] = new Album("我的相簿");
    c[1] = new Album("家人");
    c[2] = new Album("學生時代");
    c[3] = new Album("其它");
    c[4] = new Album("親愛的老婆大人");
    c[5] = new Album("寶貝女兒");
    c[6] = new Album("大學");
    c[7] = new Album("研究所");
    for (int i = 0; i < c.length; i++) {
      s.save(c[i]);
    }
 
    // create the catalog hierarchy
    c[0].addChild(c[1]);
    c[0].addChild(c[2]);
    c[0].addChild(c[3]);
    c[1].addChild(c[4]);
    c[1].addChild(c[5]);
    c[2].addChild(c[6]);
    c[2].addChild(c[7]);
 
    // 對整個物件網的 root 儲存動作,所有物件的更動都會存入資料庫
    s.save(c[0]);
 
    // create all photo objects
    Photo[] q = new Photo[3];
    q[0] = new Photo("c:\\images\\1.jpg");
    q[0].setTitle("大學畢業照!!");
 
    q[1] = new Photo("c:\\images\\2.jpg");
    q[1].setTitle("研究所畢業照!!");
 
    q[2] = new Photo("c:\\images\\3.jpg");
    q[2].setTitle("寶貝女兒滿月母女合照");
 
    for (int i = 0; i < q.length; i++) {
      s.save(q[i]);
    }
 
    // 建立 Photo 與 Album 的關聯
    c[6].addPhoto(q[0]);
    c[7].addPhoto(q[1]);
 
    q[2].addAlbum(c[4]);
    q[2].addAlbum(c[5]);
 
    // 再一次儲存全部物件
    s.save(c[0]);
 
    // 交易完成
    t.commit();
    ThreadLocalSession.closeSession();
  }
}


這裡建立測試資料的方式實在很醜,不知道有沒有人有比較好的建議。
不過要注意一點,若有兩個 domain object 要設定關聯性 (ex: obj1.addChild(obj2); ),
則至少其中之一要先用 Hibernate 存入 DB 中才行,不然會有 error。

================================================================================

作者外出取材,敬請不必耐心等候.. Tongue

現在終於懂少年快報那些連載漫畫作者的心情了...
因為實在是江郎才盡,我真想掛個牌子 作者外出取材,敬請不必耐心等候
然後就此消失,避不見面... 這樣會不會很惡劣... Big Smile

目前我還在努力搞懂 Struts 中 Blush ,而且手上的工作快要做不完了 Black Eye
先跟大家介紹我在 SourceForge 上找到的一個 project,
Java Struts-Polls http://sourceforge.net/projects/jpolls
我正在 trace 它的 code,它用到的技巧實在有夠多 (虐待我這個新手...),
有興趣可以一起討論。
要不是因為它沒什麼教學文件可看,不然我這個討論的 thread 就可以關閉了,
把它的教學文件連結過來就好了....

未完待續... but,有空再說了...

================================================================================


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
fisherman



發文: 0
積分: 0
於 2003-12-03 15:43 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
一个小问题:

在class内调用ThreadLocalSession中的方法openSession()后是否需要调用closeSession()来关闭?

我在jakarta上看到的例子是没有关闭的。


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
daisy_nhy



發文: 0
積分: 0
於 2003-12-20 18:41 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
I have some problem while running this Project,
I can run it well when I use Castor-XML
plug-in . However, when I follow the readme to replacing the Castor-XML
plug-in with the hibernate plug-in. I got the following error message.
"HTTP Status 503 - Servlet action is currently unavailable"
Could anyone can teach me how to solve my problems?


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:fisherman]
hammer





發文: 19
積分: 0
於 2004-01-02 10:31 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
fisherman wrote:
一个小问题:

在class内调用ThreadLocalSession中的方法openSession()后是否需要调用closeSession()来关闭?

我在jakarta上看到的例子是没有关闭的。


原則上session只要open後,就一定要close
即使lazy=true也是一樣


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:fisherman]
hammer





發文: 19
積分: 0
於 2004-01-02 17:20 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
fisherman wrote:
一个小问题:

在class内调用ThreadLocalSession中的方法openSession()后是否需要调用closeSession()来关闭?

我在jakarta上看到的例子是没有关闭的。


原則上session只要open後,就一定要close
即使lazy=true也是一樣


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
smallufo





發文: 57
積分: 2
於 2004-01-29 16:23 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
讓我挑一些骨頭
如果是我設計,我不會把 Photos 以及 Albums 這兩個 class 跟 hibernate dependent.
因為這樣子會增加您的 annhy.photo.domains 這個 package 與 hibernate 的相依 , 並不是好的設計

我會做一層 DAO interface , 針對 Photo / Album 做 CRUD 等動作,
例如 annhy.photo.domains.DataAccessIF.java
再利用一個 Class 實作此 DAO interface , 這個 class 最好和 annhy.photo.domains 不要放在一起 ,
例如 annhy.photo.domains.hibernate.DataAccessHibetnateImpl.java

至於 Photos 以及 Albums 這兩個 class , 只與 DAO interface 相關即可
這樣就能讓您整個 package 的設計更乾淨了...

另外一問,您的 Album 與 Photo 的相依 , 並沒有使用 lazy initialization
當'樹' 長得很大的時候,您有沒有嘗試載入 root Album ? 不怕造成 OutOfMemory 嗎?

我去年5月設計過同樣的架構,我使用 lazy="true",也遇到同樣的問題,當時找不到解答
但我的解法比較不正當,不鼓勵使用
我的解法,如果套用我剛剛說的架構
在 annhy.photo.domains.hibernate.DataAccessHibetnateImpl.java 裡面每一個 method 內, session 不要 close !
我設計一個 Servlet Filter implements javax.servlet.Filter , 在 doFilter() 中,檢查此 session 是否有綁 Hibernate 的 Session , 如果沒有,就綁上去; 如果有,就不動。
再設計一個 Servlet Listener implements HttpSessionListener , 在 sessionDestroyed() 中,把 session close 掉。

再次聲明,此法雖然可以運作,但這應該不是正確的解法。
只是當時我迫於時間壓力下想出來的解法。
應該也有許多人遇到這種問題,歡迎其他網友提供 best practice.


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:smallufo]
annhy

來呦~



發文: 45
積分: 2
於 2004-02-05 16:37 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
smallufo wrote:
讓我挑一些骨頭
如果是我設計,我不會把 Photos 以及 Albums 這兩個 class 跟 hibernate dependent.
因為這樣子會增加您的 annhy.photo.domains 這個 package 與 hibernate 的相依 , 並不是好的設計

我會做一層 DAO interface , 針對 Photo / Album 做 CRUD 等動作,
例如 annhy.photo.domains.DataAccessIF.java
再利用一個 Class 實作此 DAO interface , 這個 class 最好和 annhy.photo.domains 不要放在一起 ,
例如 annhy.photo.domains.hibernate.DataAccessHibetnateImpl.java

至於 Photos 以及 Albums 這兩個 class , 只與 DAO interface 相關即可
這樣就能讓您整個 package 的設計更乾淨了...


非常感謝您的指教,使我受益良多。


另外一問,您的 Album 與 Photo 的相依 , 並沒有使用 lazy initialization
當'樹' 長得很大的時候,您有沒有嘗試載入 root Album ? 不怕造成 OutOfMemory 嗎?

我去年5月設計過同樣的架構,我使用 lazy="true",也遇到同樣的問題,當時找不到解答
但我的解法比較不正當,不鼓勵使用
我的解法,如果套用我剛剛說的架構
在 annhy.photo.domains.hibernate.DataAccessHibetnateImpl.java 裡面每一個 method 內, session 不要 close !
我設計一個 Servlet Filter implements javax.servlet.Filter , 在 doFilter() 中,檢查此 session 是否有綁 Hibernate 的 Session , 如果沒有,就綁上去; 如果有,就不動。
再設計一個 Servlet Listener implements HttpSessionListener , 在 sessionDestroyed() 中,把 session close 掉。

再次聲明,此法雖然可以運作,但這應該不是正確的解法。
只是當時我迫於時間壓力下想出來的解法。
應該也有許多人遇到這種問題,歡迎其他網友提供 best practice.


以這個範例的規模來看,資料量是不至於會發生 OutOfMemory 的情況。
不過,我手頭上有另一個案子,它的資料量就有百萬筆的等級,
如果像這裡這樣用,一定會掛點的...

以您的解法來看,確實會有些許問題,
假設同時有 500 個 http sessions 在存取資料庫,
就會同時有 500 個 hibernate sessions,
也就是同時有 500 個 JDBC connections 被開啟,有點恐怖...。
(每個 hibernate session 中都包裝了一個 JDBC connection)
如果可以在每次 user 的 request 結束時關閉 session,
那就是接近完美的解法了。

而我自己的解法,是在所有會用到 association 的程式碼,
method 前後各自加上 ThreadLocalSession.openSession(); 與
ThreadLocalSession.closeSession();
於是就不用怕 lazy initialization 的問題了。

當然要這麼用,必須使用可以記錄 call count 的 ThreadLocalSession,
以避免巢狀呼叫時,建立兩個以上的 session。
(詳見: http://hibernate.bluemars.net/42.html 中 Steve Ebersole 的範例)
不過缺點也不小,這樣就與 hibernate 綁得更緊了....


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
enigma





發文: 4
積分: 0
於 2004-03-12 01:35 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
annhy wrote:
而我自己的解法,是在所有會用到 association 的程式碼,
method 前後各自加上 ThreadLocalSession.openSession(); 與
ThreadLocalSession.closeSession();
於是就不用怕 lazy initialization 的問題了。

當然要這麼用,必須使用可以記錄 call count 的 ThreadLocalSession,
以避免巢狀呼叫時,建立兩個以上的 session。
(詳見: http://hibernate.bluemars.net/42.html 中 Steve Ebersole 的範例)
不過缺點也不小,這樣就與 hibernate 綁得更緊了....


可以使用 Dynamic Proxy 來處理 session 的開關.


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:enigma]
KAPA





發文: 25
積分: 0
於 2004-03-12 03:12 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
enigma wrote:
可以使用 Dynamic Proxy 來處理 session 的開關.

這樣code是不是像這樣

1
2
3
4
5
6
7
8
9
10
11
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
      Object result;
        try{
          result = method.invoke(obj, args);
        }catch(InvocationTargetException e){
          throw e.getTargetException();
        }finally{
          ThreadLocalSession.closeSession();
        }
        return result;
}


然後把ThreadLocalSession.openSession();丟到每個
服務動作的開頭?譬如query,update等等的method內的開頭呢?
這樣開開關關session,會不會反而讓原本session的立意消失呀?
因為我一直以為用session包住好幾個動作在close是這個session的存在意義

like
openSession
query
update
closeSession
是他想達到的
但是用dynamic proxy變成
openSession
query
closeSession

openSession
update
closeSession
恩恩....混亂了...


KAPA edited on 2004-03-12 03:43
reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:KAPA]
enigma





發文: 4
積分: 0
於 2004-03-12 05:26 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
KAPA wrote:
這樣code是不是像這樣

1
2
3
4
5
6
7
8
9
10
11
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
      Object result;
        try{
          result = method.invoke(obj, args);
        }catch(InvocationTargetException e){
          throw e.getTargetException();
        }finally{
          ThreadLocalSession.closeSession();
        }
        return result;
}


然後把ThreadLocalSession.openSession();丟到每個
服務動作的開頭?譬如query,update等等的method內的開頭呢?


正解!! Thumbs up


這樣開開關關session,會不會反而讓原本session的立意消失呀?
因為我一直以為用session包住好幾個動作在close是這個session的存在意義

like
openSession
query
update
closeSession
是他想達到的
但是用dynamic proxy變成
openSession
query
closeSession

openSession
update
closeSession
恩恩....混亂了...


如果是 n-tier 的架構, 我個人偏好的 design 是透過 facade 把 domain object 隱藏起來. 然後再透過 dynamic proxy 來 invoke 這個 facade. 因此即使有 query + update 的狀況, 也還是只會開關 session 一次.


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
KAPA





發文: 25
積分: 0
於 2004-03-12 11:38 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
annhy wrote:
非常感謝您的指教,使我受益良多。

以這個範例的規模來看,資料量是不至於會發生 OutOfMemory 的情況。
不過,我手頭上有另一個案子,它的資料量就有百萬筆的等級,
如果像這裡這樣用,一定會掛點的...

以您的解法來看,確實會有些許問題,
假設同時有 500 個 http sessions 在存取資料庫,
就會同時有 500 個 hibernate sessions,
也就是同時有 500 個 JDBC connections 被開啟,有點恐怖...。
(每個 hibernate session 中都包裝了一個 JDBC connection)
如果可以在每次 user 的 request 結束時關閉 session,
那就是接近完美的解法了。

而我自己的解法,是在所有會用到 association 的程式碼,
method 前後各自加上 ThreadLocalSession.openSession(); 與
ThreadLocalSession.closeSession();
於是就不用怕 lazy initialization 的問題了。

當然要這麼用,必須使用可以記錄 call count 的 ThreadLocalSession,
以避免巢狀呼叫時,建立兩個以上的 session。
(詳見: http://hibernate.bluemars.net/42.html 中 Steve Ebersole 的範例)
不過缺點也不小,這樣就與 hibernate 綁得更緊了....


這邊我有點問題
應該是怕不小心把session關掉才對?
就算開很多次,但是用了ThreadLocal,照理上都會用到同一個session
感覺上是怕不小心一close,接下來的session就失效了?

我是想到像這樣
1
2
3
4
5
ThreadLocalSession.openSession();
session.save....
call method()
session.update...
ThreadLocalSession.closeSession();

然後不小心在call method()的method裡面時不小心關掉
就發生錯誤這樣??
小弟只想到這種問題~不知道我有沒有搞錯


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:enigma]
KAPA





發文: 25
積分: 0
於 2004-03-12 11:40 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
enigma wrote:
正解!! Thumbs up

如果是 n-tier 的架構, 我個人偏好的 design 是透過 facade 把 domain object 隱藏起來. 然後再透過 dynamic proxy 來 invoke 這個 facade. 因此即使有 query + update 的狀況, 也還是只會開關 session 一次.

恩~我懂意思了
下次我也這樣開發看看
感謝Smile


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
jamieweb





發文: 93
積分: 0
於 2004-04-26 20:01 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
annhy wrote:
Hibernate + Struts 學習筆記


您好, 之前我使用Castor JDO, 它的效率很差, 因為公司的資料庫table 有八十幾個,Castor一開始就load所有table進來,即使設了lazy=true,仍然很慢, 不知Hibernate的效率如何?


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:jamieweb]
aland





發文: 1
積分: 0
於 2004-05-20 13:20 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
jamieweb wrote:
您好, 之前我使用Castor JDO, 它的效率很差, 因為公司的資料庫table 有八十幾個,Castor一開始就load所有table進來,即使設了lazy=true,仍然很慢, 不知Hibernate的效率如何?


hibernate也是一开始就load所有table


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
hunk2003





發文: 8
積分: 0
於 2005-09-21 11:06 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
仁兄,笔干子很硬
希望仁兄再接再厉
把hibernate+struts示例刊出

谢谢!
luozhy2004@hotmail.com
罗忠岩


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
qrtt1





發文: 1747
積分: 31
於 2006-04-25 17:47 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
1
<property name="connection.url">jdbc:mysql://localhost/photo_db?useUnicode=true&characterEncoding=Big5</property>


&應該要改成,&amp;
不然會噴error >"<


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
cp5458





發文: 2
積分: 0
於 2006-11-19 11:31 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
文章很有帮助
多谢楼主


reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
joy6811





發文: 64
積分: 0
於 2007-02-25 15:31 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
多謝 annhy 兄 無私的分享

reply to postreply to post
作者 Re:Hibernate + Struts 學習筆記 [Re:annhy]
benjamin





發文: 16
積分: 0
於 2007-08-19 22:16 user profilesend a private message to userreply to postreply to postsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
學習中

reply to postreply to post
我不想哭、也不想认输
Welcome to MyBlog
» JWorld@TW »  Java Tools

reply to topicthreaded modego to previous topicgo to next topic
  已讀文章
  新的文章
  被刪除的文章
Jump to the top of page

JWorld@TW 本站商標資訊

Powered by Powerful JuteForum® Version Jute 1.5.8