1。簡介Hibernate
傳統的資料庫程式設計,必須直接在程式中硬編碼(hard code)SQL陳述,JDBC統一了Java程式與資料庫之間的操作介面,讓程式設計人員可以不用關係與資料庫特定相關的API操作,然而撰寫SQL陳述或自行封裝SQL仍是不可避免或必要的目標,而在物件導向程式設計中,物件與物件之間的關係,在匹配到關聯式資料庫中表格與表格之間的關係,並無法進行簡單的轉換以進行匹配。
Hibernate是「物件/關係對應」(Object/relational mapping)的解決方案,簡寫為ORM,所謂的ORM,簡單的說就是將Java中的物件與物件關係,對應到關聯式資料庫中的表格與表格之間的關係,Hibernate提供了這個過程中自動化對應轉換的方案,相反的,也提供關聯式資料庫中表格與表格之間的關係,對應至Java程式中,物件與物件的關係。
Hibernate在Java程式與資料庫之間進行轉換,Java程式設計人員只要事先定義好物件與資料庫表格之間的對應,之後Java程式設計人員可以用所熟悉的物件導向程式方法撰寫程式,而不用特定轉換SQL,所有SQL的轉換交由Hibernate進行處理。
Hibernate的官方網站在:
http://www.hibernate.org/
有關Hibernate介紹的簡體中文網站在:
http://www.hibernate.org.cn/
想要學習Hibernate,可以從官方網站的Hibernate参考手册開始,在上面的簡體中文網站中,有Hibernate參考手冊的簡體中文翻譯,這可以當作設定Hibernate相關功能時的參考手冊,書籍方面,可以看Manning的Hibernate in Action與Oreilly的Hibernate: A Developer's Notebook,Hibernate in Action當中介紹了很多關於持久層設計的觀念與理論,而A Developer's Notebook當中提供了較多實作的範例參考,另外,也可以在網路上找夏昕的Hibernate開發指南,可以讓您在短時間內瞭解Hibernate的概貌。
在我所撰寫的介紹中,主要是學習過程中,將一些配置及較關鍵性的觀念作一個記錄,但必要強調的是,有關於Hibernate的學習,並不完全在於如何設定與使用Hibernate,而是在於學習當中持久層設計的概念,這些概念如果能在學習Hibernate的過程中體會並吸收,日後即使不使用Hibernate撰寫程式,也可以發揮Hibernate持久層設計的概念於其他程式之中。
資料庫的設計是我正在學習的,有關於Hibernate持久層設計的概念,我個人的知識與學習並不足於讓我在這邊造次,這邊所建議的是看看Hibernate in Action,初看您會覺得當中儘是文字性描述,缺少範例說明,但多看個幾次,慢慢的您會發現,當中有相當多的觀念您只要瞭解,自然就知道如何使用了,設定方面通常您只要看看參考手冊按當中的說明進行就可以了。
2。Hibernate所需類別庫
Hibernate是ORM的解決方案,其底層對資料庫的操作依賴於JDBC,所以您必須先取得JDBC驅動程式,在這邊我們使用的是MySQL,所以您必須至以下網址先取得MySQL的JDBC驅動程式:
http://www.mysql.com/products/connector/j/
接下來取得Hibernate,在撰寫此文的同時,Hibernate最後的穩定版本是2.1.6,而3.0版還在測試階段,這邊的介紹將以2.1.6為主,所以請至以下網址取得hibernate-2.1.6.zip:
http://www.hibernate.org/
解開hibernate-2.1.6.zip後,當中的hibernate2.jar是必要的,而在lib目錄中還包括了許多jar檔案,其中dom4j、CGLIB、Commons Collections、Commons Logging、ODMG4、EHCache是必要的,而Log4j則是建議使用的,為何使用這些jar,在Hibernate參考手冊中有說明,您可以開啟doc/reference中的參考手冊,有英文版與簡體中文版的介紹,檔案格式則提供有html與pdf兩種,以下列出簡體中文中的說明:
dom4j(必需):Hibernate在解析XML配置和XML映射元文件時需要使用dom4j。
CGLIB(必需):Hibernate在運行時使用這個代碼生成庫強化類(與Java反射機制聯合使用)。
Commons Collections, Commons Logging(必需):Hibernat使用Apache Jakarta Commons項目提供的多個工具類庫。
ODMG4(必需):Hibernate提供了一個可選的ODMG兼容持久化管理界面。如果你需要映射集合,你就需要這個類庫,就算你不是為了使用ODMG API。
EHCache(必需):Hibernate可以使用不同的第二級Cache方案。如果沒有修改配置的話,EHCache提供默認的Cache。
Log4j(可選):Hibernate使用Commons Logging API,後者可以使用Log4j作為底層實施log的機制。如果上下文類目錄中存在Log4j庫,Commons Logging就會使用Log4j和它在上下文類路徑中找到的log4j.properties文件。在Hibernate發行包中包含有一個示例的properties文件。所以,如果你想看看幕後到底發生了什麼,也把log4j.jar拷貝到你的上下文類路徑去吧(它位於src/目錄中)。
以上是Hibernate參考手冊所列出的jar檔案,Hibernate底層還需要Java Transaction API,所以您還需要jta.jar,到這邊為止,總共需要十個jar檔案:
代碼:
mysql-connector-java-3.0.14-production-bin.jar
jta.jar
hibernate2.jar
cglib-full-2.0.2.jar
commons-collections-2.1.1.jar
commons-logging-1.0.4.jar
dom4j-1.4.jar
ehcache-0.9.jar
log4j-1.2.8.jar
odmg-3.0.jar
其它的jar檔案則視您的需要來設定,例如您應該也會使用到Ant,這對於自動化建構Hibernate有相當的幫助,您可以先查看我另一個版面上有關於Ant的介紹:
http://www.caterpillar.onlyfun.net/phpBB2/viewtopic.php?t=1354
Hibernate可以運行於單機之上,也可以運行於Web應用程式之中,如果是運行於單機,則將所有用到的jar檔案(包括JDBC驅動程式)設定至CLASSPATH中,如果是運行於Web應用程式中,則將jar檔案置放於WEB-INF/lib中,其中JDBC驅動程式也可以依賴於JNDI來取得資源,設定的方式之後介紹,或者您也可以先看看這篇文章有關於DBCP的介紹:
http://www.caterpillar.onlyfun.net/phpBB2/viewforum.php?f=29
準備好這些檔案後,我們下一個主題將介紹一個快速入門的例子。
3。第一個Hibernate程式
這邊以一個簡單的單機程式來示範Hibernate的配置與功能,在這個例子中的一些操作,實際上會使用一些自動化工具來完成,而不一定親自手動操作設定,這邊完全手動的原因,在於讓您可以知道Hibernate實際上會作那些動作,在進行範例之前,請先確定前一個主題中的相關jar檔案都已經設定在CLASSPATH中。
我們先作資料庫的準備工作,在MySQL中新增一個HibernateTest資料庫,並建立USER表格:
代碼:
CREATE TABLE USER (
user_id CHAR(32) NOT NULL PRIMARY KEY,
name VARCHAR(16) NOT NULL,
sex CHAR(1),
age INT
);
我們先撰寫一個純Java物件,它純綷表示一個資料集合,待會我們會將之映射至資料庫的表格上,程式如下:
代碼:
package onlyfun.caterpillar;
public class User {
private String id;
private String name;
private char sex;
private int age;
public int getAge() {
return age;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public char getSex() {
return sex;
}
public void setAge(int i) {
age = i;
}
public void setId(String string) {
id = string;
}
public void setName(String string) {
name = string;
}
public void setSex(char c) {
sex = c;
}
}
其中id是個特殊的屬性,Hibernate會使用它來作為主鍵識別,我們可以定義主鍵產生的方式,這是在XML映射文件中完成,為了告訴Hibernate物件如何映射至資料庫表格,我們撰寫一個XML映射文件檔名是User.hbm.xml,如下所示:
代碼:
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
這個XML文件定義了物件屬性映射至資料庫表格的關係,您可以很簡單的瞭解對映的方法,像是User物件對應至USER表格,其中我們使用uuid.hex來定義主鍵的產生算法,UUID算法使用IP地址、JVM的啟動時間、系統時間和一個計數值來產生主鍵。除了使用uuid.hex之外,我們還可以使用其它的方式來產生主鍵,像是increment等,這可以在Hibernate參考手冊中找到相關資料。
接下來我們定義Hibernate配置文件,主要是進行SessionFactory配置,Hibernate可以使用XML或屬性文件來進行配置,我們這邊先介紹如何使用XML配置,這也是Hibernate所建議的配置方式,我們的檔名是hibernate.cfg.xml,如下:
代碼:
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
接下來我們撰寫一個測試的程式,這個程式將直接以Java程式設計人員熟悉的語法方式來操作物件,而實際上也直接完成對資料庫的操作,程式將會將一筆資料存入表格之中:
代碼:
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
User user = new User();
user.setName("caterpillar");
user.setSex('M');
user.setAge(28);
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(user);
tx.commit();
session.close();
sessionFactory.close();
System.out.println("新增資料OK!請先用MySQL觀看結果!");
}
}
Configuration代表了Java物件至資料庫的映射設定,這個設定是從我們上面的XML而來,接下來我們從Configuration取得SessionFactory物件,並由它來開啟一個Session,它代表物件與表格的一次會話操作,而Transaction則表示一組會話操作,我們只需要直接操作User物件,並進行Session與Transaction的相關操作,Hibernate就會自動完成對資料庫的操作。這邊對程式先只作簡單的介紹,之後再詳加說明。
將所有的.java檔案編譯,並將兩個XML檔案放置在與HibernateTest相同的目錄中,也就是檔案位置如下:
代碼:
/
|--HibernateTest.class
|--User.hbm.xml
|--hibernate.cfg.xml
/onlyfun
/caterpillar
|--User.class
OK!現在您可以執行HibernateTest,程式將會出現以下的訊息:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: insert into USER (name, sex, age, user_id) values (?, ?, ?, ?)
新增資料OK!請先用MySQL觀看結果!
這邊只先進行資料的存入,要觀看資料存入的結果的話,請進入MySQL觀看,以下是資料庫存入的結果:
代碼:
mysql> SELECT * FROM USER;
+----------------------------------+-------------+------+------+
| user_id | name | sex | age |
+----------------------------------+-------------+------+------+
| 297e3dbdfea6023d00fea60241000001 | caterpillar | M | 28 |
+----------------------------------+-------------+------+------+
1 rows in set (0.00 sec)
4。配置文件
Hibernate可以使用XML或屬性檔案來配置SessionFactory,預設的配置文件名稱為hibernate.cfg.xml或hibernate.properties。
上一個主題中所示範的為使用XML文件的方式,一個XML文件的例子如下:
代碼:
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
使用XML文件進行配置時,可以在當中指定物件與資料庫表格的映射文件位置,XML配置文件的位置必須在CLASSPATH的設定中,例如單機執行時主程式的位置,或是Web程式的WEB-INF/classes中,我們使用下面的方式來讀入XML文件以配置Hibernate:
代碼:
SessionFactory sf = new Configuration().configure().buildSessionFactory();
Configuration表示Java物件與資料庫表格映射的集合,並用於之後建立SessionFactory,之後Configuration就不再有作用。預設的XML文件名稱是hibernate.cfg.xml,您也可以指定文件的名稱,例如:
代碼:
SessionFactory sf = new Configuration()
.configure("db.cfg.xml")
.buildSessionFactory();
除了使用XML文件進行配置,我們也可以使用屬性檔案進行配置,檔案名稱是hibernate.properties,一個例子如下:
代碼:
hibernate.show_sql = true
hibernate.dialect = net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost/HibernateTest
hibernate.connection.username = caterpillar
hibernate.connection.password = 123456
hibernate.properties的位置必須在CLASSPATH的設定中,例如單機執行時主程式的位置,或是Web程式的WEB-INF/classes中,而為了要取得物件至資料庫表格的映射文件,我們必須在程式中如下載入:
代碼:
Configuration cfg = new Configuration()
.addClass(onlyfun.caterpillar.User.class)
.addClass(onlyfun.caterpillar.Item.class);
這麼一來,程式會自動載入onlyfun/caterpillar/User.hbm.xml與onlyfun/caterpillar/Item.hbm.xml,完成Hibernate配置之後,我們可以如下取得SessionFactory:
代碼:
SessionFactory sessions = cfg.buildSessionFactory();
其它更多有關Hibernate配置的細節,您可以查看Hibernate參考手冊。
5。提供JDBC連接
如果需要的話,您可以自行提供JDBC連接物件給Hibernate使用,而無需透過配置文件設定JDBC來源,一個最簡單的例子如下:
代碼:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/HibernateTest?user=root&password=";
java.sql.Connection conn = DriverManager.getConnection(url);
SessionFactory sessionFactory = cfg.buildSessionFactory();
Session session = sessionFactory.openSession(conn);
當然您也可以透過屬性文件hibernate.properties來配置JDBC來源,例如:
代碼:
hibernate.show_sql = true
hibernate.dialect = net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost/HibernateTest
hibernate.connection.username = caterpillar
hibernate.connection.password = 123456
如果是透過XML文件hibernate.cfg.xml則是如下進行配置:
代碼:
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
Hibernate在資料庫連接池的使用上是可選的,您可以使用C3P0連接池,當您的屬性文件中含有hibernate.c3p0.*的配置時,就會自動啟用C3P0連接池,而您的CLASSPATH中必須包括c3p0-0.8.4.5.jar,屬性文件hibernate.properties的配置範例如下:
代碼:
hibernate.show_sql = true
hibernate.dialect = net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost/HibernateTest
hibernate.connection.username = root
hibernate.connection.password =
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statements=50
如果是使用hibernate.cfg.xml配置C3P0連接池的例子如下:
代碼:
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
您也可以使用Proxool或DBCP連接池,只要在配置文件中配置hibernate.proxool.*或hibernate.dbcp.*等相關選項,這可以在hibernate的etc目錄中找hibernate.properties中的配置例子來參考,當然要記得在CLASSPATH中加入相關的jar檔案。
如果您使用Tomcat的話,您也可以透過它提供的DBCP連接池來取得連接,您可以先參考這邊的文章來設定Tomcat的DBCP連接池:
http://www.caterpillar.onlyfun.net/phpBB2/viewtopic.php?t=1354
設定好容器提供的DBCP連接池之後,您只要在配置文件中加入connection.datasource屬性,例如在hibernate.cfg.xml中加入:
代碼:
如果是在hibernate.properties中的話,則加入:
代碼:
hibernate.connection.datasource = java:comp/env/jdbc/dbname
6。基本資料查詢
使用Hibernate進行資料查詢是一件簡單的事,Java程式設計人員可以使用物件操作的方式來進行資料查詢,查詢時使用一種類似SQL的HQL(Hibernate Query Language)來設定查詢的條件,與SQL不同的是,HQL是具備物件導向的繼承、多型等特性的語言。
直接使用範例來看看如何使用Hibernate進行資料庫查詢,在這之前,請先照之前介紹過的主題在資料庫中新增幾筆資料:
http://www.caterpillar.onlyfun.net/phpBB2/viewtopic.php?t=1375
查詢資料時,我們所使用的是Session的find()方法,並在當中指定HQL設定查詢條件,查詢的結果會裝載在List物件中傳回,您所需要的是將它們一一取出,一個最簡單的例子如下:
代碼:
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"/n/tAge: " + user.getAge() +
"/n/tSex: " + user.getSex());
}
}
}
find()中的"from User"即HQL,User指的是User類別,藉由映射文件,它將會查詢USER表格中的資料,相當於SQL中的SELECT * FROM USER,實際上我們的User類別是位於onlyfun.caterpillar下,Hibernate會自動看看import中的package名稱與類別名稱是否符合,您也可以直接指定package名稱,例如:
代碼:
session.find("from onlyfun.caterpillar.User");
這個程式的運行結果可能是這樣的:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
caterpillar
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
上面所介紹的查詢是最簡單的,只是從資料表中查詢所有的資料,Hibernate所查詢得回的資料,是以物件的方式傳回,以符合程式中操作的需要,我們也可以限定一些查詢條件,並只傳回我們指定的欄位,例如:
代碼:
List names = session.find("select user.name from User as user where age = 25");
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) {
String name = (String) iterator.next();
System.out.println("name: " + name);
}
在find()中的HQL示範了條件限定的查詢,User as user為User類別取了別名,所以我們就可以使用user.name來指定表格傳回欄位,where相當於SQL中的WHERE子句,我們限定查詢age等於25的資料,這次查詢的資料只有一個欄位,而型態是String,所以傳回的List內容都是String物件,一個運行的例子如下:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.name as x0_0_ from USER user0_ where (age=25 )
name: momor
name: Bush
如果要傳回兩個以上的欄位,也不是什麼問題,直接來看個例子:
代碼:
List results = session.find("select user.name, user.age from User as user where sex = 'F'");
for (ListIterator iterator = results.listIterator(); iterator.hasNext(); ) {
Object[] rows = (Object[]) iterator.next();
String name = (String) rows[0];
Integer age = (Integer) rows[1];
System.out.println("name: " + name + "/n/t" + age);
}
從上面的程式中不難看出,傳回兩個以上欄位時,每一次ListIterator會以Object陣列的方式傳回一筆資料,我們只要指定陣列索引,並轉換為適當的型態,即可取得資料,一個查詢的結果如下:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.name as x0_0_, user0_.age as x1_0_ from USER user0_ where (sex='F' )
name: momor
25
name: Becky
35
您也可以在HQL中使用一些函數來進行結果統計,例如:
代碼:
List results = session.find("select count(*), avg(user.age) from User as user");
ListIterator iterator = results.listIterator();
Object[] rows = (Object[]) iterator.next();
System.out.println("資料筆數: " + rows[0] + "/n平均年齡: " + rows[1]);
一個查詢的結果如下所示:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select count(*) as x0_0_, avg(user0_.age) as x1_0_ from USER user0_
資料筆數: 4
平均年齡: 28.25
這邊我們先介紹的是一些簡單的查詢動作,將來有機會的話,再介紹一些進階的查詢,如果您有想要先認識一些HQL,可以看看參考手冊的第11章,當中對於HQL有詳細的說明。
7。Query介面
除了直接使用find()方法並配合HQL來進行查詢之外,我們還可以透過net.sf.hibernate.Query介面的實例來進行查詢,透過Query介面,您可以先設定查詢參數,之後透過setXXX()等方法,將指定的參數值填入,而不用每次都撰寫完整的HQL,直接來看個例子:
代碼:
Query query = session.createQuery("select user.name from User as user where user.age = ? and user.sex = ?");
query.setInteger(0, 25);
query.setCharacter(1, 'M');
List names = query.list();
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) {
String name = (String) iterator.next();
System.out.println("name: " + name);
}
在設定參數值時,必須依照 ? 所設定的順序,並使用對應型態的setXXX()方法,一個執行的例子如下:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.name as x0_0_ from USER user0_ where (user0_.age=? )and(user0_.sex=? )
name: Bush
您可以使用命名參數(Named Parameter)來取代這個方法,這可以不用依照特定的順序來設定參數值,並擁有較好的可讀性,直接來看個例子:
代碼:
Query query = session.createQuery("select user.name from User as user where user.age = :age and user.sex = :sex");
query.setInteger("age", 25);
query.setCharacter("sex", 'M');
List names = query.list();
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) {
String name = (String) iterator.next();
System.out.println("name: " + name);
}
設定命名參數時,在建立Query時先使用:後跟著參數名,之後我們就可以在setXXX()方法中直接指定參數名來設定參數值,而不用依照特定的順序。
我們也可以將HQL撰寫在程式之外,以避免硬編碼(hard code)在程式之中,在需要修改HQL時就很方便,在*.hbm.xml中使用
代碼:
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
select user.name from User as user where user.age = :age and user.sex = :sex
]]>
代碼:
Query query = session.getNamedQuery("onlyfun.caterpillar.queryUser");
query.setInteger("age", 25);
query.setCharacter("sex", 'M');
List names = query.list();
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) {
String name = (String) iterator.next();
System.out.println("name: " + name);
}
8。 更新、刪除資料
如果您是在同一個Session中取出資料並想要馬上進行更新,則只要先查詢並取出物件,透過setXXX()方法設定好新的值,然後呼叫session.flush()即可在同一個Session中更新指定的資料,例如:
代碼:
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
User updated = null;
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
if(updated == null)
updated = user;
System.out.println(user.getName() +
"/n/tAge: " + user.getAge() +
"/n/tSex: " + user.getSex());
}
updated.setName("justin");
session.flush();
users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"/n/tAge: " + user.getAge() +
"/n/tSex: " + user.getSex());
}
}
}
這個程式會顯示資料表中的所有資料,並將資料表中的第一筆資料更新,一個執行的結果如下:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
caterpillar
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate: update USER set name=?, sex=?, age=? where user_id=?
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
justin
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
如果您開啟了一個Session,從資料表中取出資料顯示到使用者介面上,之後關閉Session,當使用者在介面上操作完畢並按下儲存時,這時您要重新開啟一個Session,使用update()方法將物件中的資料更新至對應的資料表中,一個例子如下:
代碼:
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
// 關閉這個Session
session.close();
User updated = null;
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
if(updated == null)
updated = user;
System.out.println(user.getName() +
"/n/tAge: " + user.getAge() +
"/n/tSex: " + user.getSex());
}
// 使用者作一些操作,之後儲存
updated.setName("caterpillar");
// 開啟一個新的Session
session = sessionFactory.openSession();
// 更新資料
session.update(updated);
users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"/n/tAge: " + user.getAge() +
"/n/tSex: " + user.getSex());
}
}
}
這個程式執行的結果範例如下,您可以看看實際上執行了哪些SQL:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
justin
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate: update USER set name=?, sex=?, age=? where user_id=?
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
caterpillar
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate提供了一個saveOrUpdate()方法,為資料的儲存或更新提供了一個統一的操作介面,藉由定義映射文件時,設定
代碼:
unsaved-value可以設定的值包括:
*any - 總是儲存
*none - 總是更新
*null - id為null時儲存(預設)
*valid - id為null或是指定值時儲存
這樣設定之後,您可以使用session.saveOrUpdate(updated);來取代上一個程式的session.update(updated);方法。
如果要刪除資料,只要使用delete()方法即可,直接看個例子。
代碼:
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
User updated = null;
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
if(updated == null)
updated = user;
System.out.println(user.getName() +
"/n/tAge: " + user.getAge() +
"/n/tSex: " + user.getSex());
}
session.delete(updated);
users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"/n/tAge: " + user.getAge() +
"/n/tSex: " + user.getSex());
}
}
}
一個執行的結果範例如下:
代碼:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
justin
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate: delete from USER where user_id=?
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate對於資料的更新、刪除等動作,是依懶id值來判定,如果您已知id值,則可以使用load()方法來載入資料,例如:
代碼:
User user = (User) session.load(User.class, id);
9. Hibernate Gossip - 繼承映射(1)
如果應用程式中的物件有繼承的關係,我們可以有三種策略將這種關係映射至資料表上。
最簡單的方式就是給每個物件一個表格,如果父類別User中有field1、field2兩個屬性,其表格USER有FIELD1、FIELD2與之對應,而子類別SubUser若繼承了父類別的field1、field2屬性,表格中SUBUSER中也要擁有FIELD1、FIELD2與之對應,這種方法的好處只有映射上的方便,很顯然的,父類與子類共有的屬性,會變成在資料庫表格中重複的欄位,而且很難實現多型操作,建議只有在不需要多型操作時使用,要執行這種映射,為每一個子類別撰寫一個映射文件就是了,沒什麼特別的設定。
第二種方式是將所有繼承同一父類別的物件儲存在同一個表格中,表格中使用識別欄位來表示某一列(row)是屬於某個子類別或父類別,這種方式方便執行多型操作,而且兼具效能上的考量,在這個主題中我們將先說明這個方法。
我們先來看看我們撰寫的類別與繼承關係,首先是父類別:
代碼:
package onlyfun.caterpillar;
public class User {
private String id;
private String name;
private String password;
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public void setId(String string) {
id = string;
}
public void setName(String string) {
name = string;
}
public void setPassword(String password) {
this.password = password;
}
}
再來是繼承User類別的兩個子類別,首先是PowerUser類別:
代碼:
package onlyfun.caterpillar;
public class PowerUser extends User {
private int level;
private String otherOfPower;
public int getLevel() {
return level;
}
public String getOtherOfPower() {
return otherOfPower;
}
public void setLevel(int level) {
this.level = level;
}
public void setOtherOfPower(String otherOfPower) {
this.otherOfPower = otherOfPower;
}
}
下面是繼承User類別的GuestUser類別:
代碼:
package onlyfun.caterpillar;
public class GuestUser extends User {
private String otherOfGuest;
public String getOtherOfGuest() {
return otherOfGuest;
}
public void setOtherOfGuest(String otherOfGuest) {
this.otherOfGuest = otherOfGuest;
}
}
映射文件中該如何撰寫,由於這些類別將映射至同一個表格,我們使用discriminator作為每個類別記錄在表格中的識別,先直接看看映射文件如何撰寫:
代碼:
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
在表格中,我們增加一個欄位DISCRIMINATOR_USERTYPE來記錄儲存的類別是屬於User、PowerUser或是GuestUser的記錄,如果該欄位是ParentUser,則表示該筆資料是User類別,如果是POWER,表示是PowerUser的記錄,如果是GUEST,表示是GuestUser的記錄,在映射子類別時,使用
我們可以在資料庫中建立資料表格如下:
代碼:
create table USER (
ID char(32) not null,
DISCRIMINATOR_USERTYPE varchar(255) not null,
NAME varchar(16) not null,
PASSWORD varchar(16) not null,
POWERUSER_LEVEL integer,
POWER_OTHER varchar(255),
GUEST_OTHER varchar(255),
primary key (ID)
);
您可以將資料表的建立工作,透過SchemaExportTask來自動建立,您可以參考這篇介紹:
http://www.caterpillar.onlyfun.net/phpBB2/viewtopic.php?t=1383
假設我們在程式中如下儲存資料的話:
代碼:
PowerUser pu = new PowerUser();
pu.setName("caterpillar");
pu.setPassword("123456");
pu.setLevel(1);
pu.setOtherOfPower("PowerUser's field");
GuestUser gu = new GuestUser();
gu.setName("momor");
gu.setPassword("654321");
gu.setOtherOfGuest("GuestUser's field");
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(pu);
session.save(gu);
tx.commit();
session.close();
則資料表中將會有以下的內容(沒有顯示ID欄位):
代碼:
+------------------------+-------------+----------+-----------------+-------------------+-------------------+
| DISCRIMINATOR_USERTYPE | NAME | PASSWORD | POWERUSER_LEVEL | POWER_OTHER | GUEST_OTHER |
+------------------------+-------------+----------+-----------------+-------------------+-------------------+
| POWER | caterpillar | 123456 | 1 | PowerUser's field | NULL |
| GUEST | momor | 654321 | NULL | NULL | GuestUser's field |
+------------------------+-------------+----------+-----------------+-------------------+-------------------+
您可以觀察實際的儲存方式,注意DISCRIMINATOR_USERTYPE欄位,它用以標示該列屬於哪一個類別的資料,如果要查詢資料的話,例如查詢所有PowerUser的資料,我們只要如下進行:
代碼:
Session session = sessionFactory.openSession();
List users = session.find("from PowerUser");
session.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
PowerUser user = (PowerUser) iterator.next();
System.out.println(user.getName() +
"/n/tPassword: " + user.getPassword());
System.out.println("/tPower: " + user.getOtherOfPower() +
"/n/tLevel: " + user.getLevel());
}
您可以觀察Hibernate真正所執行的SQL的內容,看看where就知道它如何查詢PowerUser的資料:
代碼:
select poweruser0_.ID as ID,
poweruser0_.POWERUSER_LEVEL as POWERUSE5_,
poweruser0_.POWER_OTHER as POWER_OT6_,
poweruser0_.NAME as NAME,
poweruser0_.PASSWORD as PASSWORD
from USER poweruser0_ where poweruser0_.DISCRIMINATOR_USERTYPE='POWER';
使用session.find("from GuestUser");就可以查詢GuestUser的資料,您也可以取回所有User型態的資料,例如:
代碼:
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"/n/tPassword: " + user.getPassword());
if(user instanceof PowerUser)
System.out.println("/tPower: " + ((PowerUser)user).getOtherOfPower() +
"/n/tLevel: " + ((PowerUser)user).getLevel());
else
System.out.println("/tGuest: " + ((GuestUser)user).getOtherOfGuest());
}
Hibernate可以使用父類別來取得所有的子類別資料,我們知道所有的Java類別都繼承自Object,所以如果您使用session.find("from java.lang.Object");,就將會取回資料庫中所有表格的資料。
10 .Hibernate Gossip - 繼承映射(2)
接續上一個主題,我們來看看繼承關係映射的第三種方式,我們給予父類別與每個子類別一個表格,與第一個方法不同的是,父類別映射的表格與子類別映射的表格共享相同的主鍵值,父類別表格只記錄本身的屬性,如果要查詢的是子類別,則透過外鍵參考從父類別表格中取得繼承而來的屬性資料。
直接以圖片說明會比較容易理解,我們使用前一個主題中的User、PowerUser與GuestUser類別作說明,類別繼承圖如下:
其映射至資料庫表格的關係如下:
其中POWER_USER與GUEST_USER表格的主鍵值將與USER表格的主鍵值相同,POWER_USER_ID與GUEST_USER_ID作為一個外鍵參考,以取得父類別映射表格的NAME與PASSWORD資料。
在映射文件中要實現這種映射,我們使用
代碼:
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
您可以自行建立資料庫與表格,當然使用SchemaExportTask幫您自動建立表格是更方便的,下面是SchemaExportTask自動建立表格時所使用的SQL:
代碼:
[schemaexport] alter table GUEST_USER drop constraint FKB62739F25ED19688;
[schemaexport] alter table POWER_USER drop constraint FK38F5D2E586CA74B5;
[schemaexport] drop table if exists USER;
[schemaexport] drop table if exists GUEST_USER;
[schemaexport] drop table if exists POWER_USER;
[schemaexport] create table USER (
[schemaexport] USER_ID varchar(255) not null,
[schemaexport] NAME varchar(16) not null,
[schemaexport] PASSWORD varchar(16) not null,
[schemaexport] primary key (USER_ID)
[schemaexport] );
[schemaexport] create table GUEST_USER (
[schemaexport] GUEST_USER_ID varchar(255) not null,
[schemaexport] GUEST_OTHER varchar(255),
[schemaexport] primary key (GUEST_USER_ID)
[schemaexport] );
[schemaexport] create table POWER_USER (
[schemaexport] POWER_USER_ID varchar(255) not null,
[schemaexport] POWER_USER_LEVEL integer,
[schemaexport] POWER_OTHER varchar(255),
[schemaexport] primary key (POWER_USER_ID)
[schemaexport] );
[schemaexport] alter table GUEST_USER add index FKB62739F25ED19688 (GUEST_USER_ID), add constraint FKB62739F25ED19688 foreign key (GUEST_USER_ID) references USER (USER_ID);
[schemaexport] alter table POWER_USER add index FK38F5D2E586CA74B5 (POWER_USER_ID), add constraint FK38F5D2E586CA74B5 foreign key (POWER_USER_ID) references USER (USER_ID);
至於在Java程式的撰寫方面,您可以直接使用前一個主題中所寫的測試程式(您可以看到,Hibernate將程式撰寫與資料庫處理的細節分開了),假設我們使用上一個主題的測試程式新增了兩筆資料,則資料庫表格的結果如下:
代碼:
mysql> select * from user;
+----------------------------------+-------------+----------+
| USER_ID | NAME | PASSWORD |
+----------------------------------+-------------+----------+
| 297e3dbdff0af72900ff0af72d4b0001 | caterpillar | 123456 |
| 297e3dbdff0af72900ff0af72d4b0002 | momor | 654321 |
+----------------------------------+-------------+----------+
2 rows in set (0.05 sec)
mysql> select * from power_user;
+----------------------------------+------------------+-------------------+
| POWER_USER_ID | POWER_USER_LEVEL | POWER_OTHER |
+----------------------------------+------------------+-------------------+
| 297e3dbdff0af72900ff0af72d4b0001 | 1 | PowerUser's field |
+----------------------------------+------------------+-------------------+
1 row in set (0.00 sec)
mysql> select * from guest_user;
+----------------------------------+-------------------+
| GUEST_USER_ID | GUEST_OTHER |
+----------------------------------+-------------------+
| 297e3dbdff0af72900ff0af72d4b0002 | GuestUser's field |
+----------------------------------+-------------------+
1 row in set (0.00 sec)
瞭解一下儲存資料至資料庫時所使用的SQL語句有助於您瞭解底層的運作方式,新增兩筆資料的SQL如下:
代碼:
Hibernate: insert into USER (NAME, PASSWORD, USER_ID) values (?, ?, ?)
Hibernate: insert into POWER_USER (POWER_USER_LEVEL, POWER_OTHER, POWER_USER_ID) values (?, ?, ?)
Hibernate: insert into USER (NAME, PASSWORD, USER_ID) values (?, ?, ?)
Hibernate: insert into GUEST_USER (GUEST_OTHER, GUEST_USER_ID) values (?, ?)
有興趣的話,也可以看一下查詢資料時的SQL語句,我們可以看到實際上使用inner join來查詢資料,以下是查詢PowerUser所使用的SQL:
代碼:
select poweruser0_.POWER_USER_ID as USER_ID,
poweruser0_.POWER_USER_LEVEL as POWER_US2_1_,
poweruser0_.POWER_OTHER as POWER_OT3_1_,
poweruser0__1_.NAME as NAME0_,
poweruser0__1_.PASSWORD as PASSWORD0_
from POWER_USER poweruser0_
inner join USER poweruser0__1_ on poweruser0_.POWER_USER_ID=poweruser0__1_.USER_ID
11. Hibernate Gossip - Component 映射
考慮這麼一個物件類別:
代碼:
package onlyfun.caterpillar;
public class User {
private String id;
private String name;
private char sex;
private int age;
private String address;
........
}
我們建立一個資料庫表格與這個類別作對應:
代碼:
create table USER (
ID char(32) not null,
NAME varchar(16) not null,
SEX char(1),
AGE integer,
ADDRESS varchar(255) not null,
primary key (ID)
);
這樣的對應,您可以依照之前所介紹過的主題來撰寫映射文件達成,然而現在我們想要將address屬性以一個自訂物件Email表示,以便我們將這個自訂物件當作輔助物件,在適當的時候,我們可以直接呼叫Email物件的sendMail()方法,也就是說,我們的User類別變成:
代碼:
package onlyfun.caterpillar;
public class User {
private String id;
private String name;
private char sex;
private int age;
private Email email;
public int getAge() {
return age;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public char getSex() {
return sex;
}
public Email getEmail() {
return email;
}
public void setAge(int i) {
age = i;
}
public void setId(String string) {
id = string;
}
public void setName(String string) {
name = string;
}
public void setSex(char c) {
sex = c;
}
public void setEmail(Email email) {
this.email = email;
}
}
而我們新增的Email類別設計如下:
代碼:
package onlyfun.caterpillar;
public class Email {
private String address;
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
public void sendMail() {
System.out.println("send mail to: " + address);
}
}
實際上,我們只是將使用者的Email資訊抽取出來,並獨立為一個輔助物件,這是為了在應用程式中操作方便而設計的,而實際上在資料庫表格中的資訊並沒有增加,我們不用改變資料表的內容。
顯然的,如果這麼設計,物件與資料表並不是一個對映一個,物件的數量將會比資料表來的多,物件的設計粒度比資料表來的細,為了完成物件與資料表的對應,在Hibernate中使用
代碼:
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
這個映射文件與之前的映射文件撰寫相比,主要是多了紅色的
接下來對物件的操作並沒有什麼太大的差別,例如下面的程式片段示範如何儲存資料:
代碼:
User user = new User();
user.setName("caterpillar");
user.setSex('M');
user.setAge(28);
Email email = new Email();
email.setAddress("[email protected]");
user.setEmail(email);
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(user);
tx.commit();
session.close();
下面的程式片段則示範如何查詢資料:
代碼:
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"/n/tAge: " + user.getAge() +
"/n/tSex: " + user.getSex());
user.getEmail().sendMail();
}
12 . Hibernate Gossip - Set 映射
這個主題介紹如果在物件中包括集合物件,像是使用HashSet來包括其它物件時,該如何進行物件與資料表的映射,像Set這樣的集合,可以包括所有的Java物件,這邊先介紹當Set中包括的物件沒有實體(Entiy)時的映射方式。
(簡單的說,也就是所包括的物件沒有物件識別(identity)值,沒有資料庫層次上的識別值之表格與之對應的物件,只是純綷的值型態(value type)物件,關於Entity與value type的說明,可以看看參考手冊5.2.1或是Hibernate in Action的第六章。)
假設我們有一個User類別,當中除了名稱屬性之外,另一個就是使用者的電子郵件位址,同一個使用者可能有多個不同的郵件地址,所以我們在User類別中使用Set物件來加以記錄,在這邊我們使用String來記錄每一筆郵件位址,為了不允許重複的郵件位址記錄,所以我們使用Set物件,我們的User類別如下:
代碼:
package onlyfun.caterpillar;
import java.util.HashSet;
import java.util.Set;
public class User {
private long id;
private String name;
private Set addrs = new HashSet();
public Set getAddrs() {
return addrs;
}
public void setAddrs(Set addrs) {
this.addrs = addrs;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addAddress(String addr) {
addrs.add(addr);
}
}
addAddress()方法是為了加入一筆一筆的郵件位址而另外增加的,我們也可以在外部設定好Set物件,再使用setAddrs()方法設定給User物件,在映射文件上,為了進行Set的映射,我們使用
代碼:
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
從映射文件中我們可以看到,我們使用另一個表格ADDRS來記錄Set中真正記錄的物件,為了表明ADDRS中的每一筆資料是屬於哪一個USER的,我們透過ADDRS的外鍵USER_ID參考至USER的USER_ID,ADDRS的USER_ID與USER_ID的內容將會是相同的,而
假設我們使用下面的程式來儲存User的資料:
代碼:
User user1 = new User();
user1.setName("caterpillar");
user1.addAddress("[email protected]");
user1.addAddress("[email protected]");
user1.addAddress("[email protected]");
User user2 = new User();
user2.setName("momor");
user2.addAddress("[email protected]");
user2.addAddress("[email protected]");
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(user1);
session.save(user2);
tx.commit();
session.close();
實際上在資料庫中的USER與ADDRS表格的內容將如下:
代碼:
mysql> select * from user;
+---------+-------------+
| USER_ID | NAME |
+---------+-------------+
| 1 | caterpillar |
| 2 | momor |
+---------+-------------+
2 rows in set (0.00 sec)
mysql> select * from addrs;
+---------+-------------------------------------+
| USER_ID | ADDRESS |
+---------+-------------------------------------+
| 1 | [email protected] |
| 1 | [email protected] |
| 1 | [email protected] |
| 2 | [email protected] |
| 2 | [email protected] |
+---------+-------------------------------------+
5 rows in set (0.00 sec)
下面的程式則簡單的示範如何取出資料:
代碼:
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName());
Object[] addrs = user.getAddrs().toArray();
for(int i = 0; i < addrs.length; i++) {
System.out.println("/taddress " + (i+1) + ": " + addrs);
}
}
13 . Hibernate Gossip - List 映射
這邊介紹如果物件中包括List型態的屬性時如何進行映射,首先我們假設我們要製作一個線上檔案管理,使用者上載的檔案名稱可能是重複的
、具有相同名稱,之前使用的Set不允許有重複的內容,所以這次我們改用List,我們的User類別撰寫如下:
代碼:
package onlyfun.caterpillar;
import java.util.*;
public class User {
private long id;
private String name;
private List files = new ArrayList();
public List getFiles() {
return files;
}
public void setFiles(List files) {
this.files = files;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addFiles(String name) {
files.add(name);
}
public void addFiles(int i, String name) {
files.add(i, name);
}
}
我們在程式中使用的是ArrayList,要在Hibernate中將之映射至資料庫,基本上與映射Set相同,我們使用標籤替代
代碼:
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
與Set類似的是,記錄List的FILES表格中,使用與USER表格相同的USER_ID值,我們來看看資料儲存至資料表中是如何,假設我們的程式以下面的方式儲存資料:
代碼:
User user1 = new User();
user1.setName("caterpillar");
user1.addFiles("hibernate2.jar");
user1.addFiles("jtx.jar");
User user2 = new User();
user2.setName("momor");
user2.addFiles("fan.jpg");
user2.addFiles("fan.jpg");
user2.addFiles("bush.jpg");
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(user1);
session.save(user2);
tx.commit();
session.close();
那麼在資料庫中的USER與FILES表格將儲存以下的內容:
代碼:
mysql> select * from user;
+---------+-------------+
| USER_ID | NAME |
+---------+-------------+
| 1 | caterpillar |
| 2 | momor |
+---------+-------------+
2 rows in set (0.00 sec)
mysql> select * from files;
+---------+----------------+----------+
| USER_ID | FILENAME | POSITION |
+---------+----------------+----------+
| 1 | hibernate2.jar | 0 |
| 1 | jtx.jar | 1 |
| 2 | fan.jpg | 0 |
| 2 | fan.jpg | 1 |
| 2 | bush.jpg | 2 |
+---------+----------------+----------+
5 rows in set (0.00 sec)