Hibernate對JDBC進行了封裝,提供了更加面向對象的API。圖2-4和圖2-5對比了直接通過JDBC API及通過Hibernate API來訪問數據庫的兩種方式。



圖2-4 通過JDBC API訪問數據庫



圖2-5 通過Hibernate API訪問數據庫

以下例程2-4的BusinessService類演示了通過Hibernate API對Customer對象進行持久化的操作。本章2.4節(jié)提到Hibernate沒有滲透到域模型中,即在持久化類中沒有引入任何Hibernate API。但是對于應用中負責處理業(yè)務的過程域對象,當然應該借助Hibernate API來操縱數據庫。例程2-4 BusinessService.java:

package mypack;
import javax.servlet.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.io.*;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.*;

public class BusinessService{
  public static SessionFactory sessionFactory;
  
/** 初始化Hibernate,創(chuàng)建SessionFactory實例 */
  static{
    try{
      // 根據默認位置的Hibernate配置文件的配置信息,
	  創(chuàng)建一個Configuration實例
      Configuration config = new Configuration();
      config.addClass(Customer.class);
      
// 創(chuàng)建SessionFactory實例 */
      sessionFactory = config.buildSessionFactory();
    }catch(Exception e){e.printStackTrace();}
  }
  
/** 查詢所有的Customer對象,
然后調用printCustomer()方法打印Customer對象信息 */
  public void findAllCustomers
  (ServletContext context,OutputStream out)
  throws Exception{…… }
  
  /** 持久化一個Customer對象 *./
  public void saveCustomer(Customer customer)
  throws Exception{…… }

  /** 按照OID加載一個Customer對象,然后修改它的屬性 */
  public void loadAndUpdateCustomer
  (Long customer_id,String address)
  throws Exception{……}
 
/**刪除所有的Customer對象 */
  public void deleteAllCustomers()
  throws Exception
  {
    Session session 
	= sessionFactory.openSession();
    Transaction tx = null;
    try {
      tx = session.beginTransaction();
      session.delete("from Customer as c");
      tx.commit();
    }catch (Exception e) {
      if (tx != null) {
        tx.rollback();
      }
      throw e;
    } finally {
      session.close();
    }
  }
 
  /** 選擇向控制臺還是動態(tài)網頁
  輸出Customer對象的信息 */
  private void printCustomer
  (ServletContext context,OutputStream out,
  Customer customer)
throws Exception{
  	if(out instanceof ServletOutputStream)
          printCustomer(context,
		  (ServletOutputStream) out,customer);
    else
          printCustomer((PrintStream)
		  out,customer);
  }

   /** 把Customer對象的信息輸出到控制臺,
   如DOS 控制臺*/
  private void printCustomer
  (PrintStream out,Customer customer)throws 
  Exception{…… }
  
  /** 把Customer對象的信息輸出到動態(tài)網頁 */
private void printCustomer
(ServletContext context,
ServletOutputStream out,Customer customer)
throws Exception{……}

  public void test
  (ServletContext context,OutputStream out)
  throws Exception
  {
    Customer customer=new Customer();
    customer.setName("Tom");
    customer.setEmail("tom@yahoo.com");
    customer.setPassword("1234");
    customer.setPhone(55556666);
    customer.setAddress("Shanghai");
    customer.setSex('M');
    customer.setDescription("I am very honest.");

//設置Customer對象的image屬性,
它是字節(jié)數組,存放photo.gif文件中的二進制數據
//photo.gif文件和BusinessService.class
文件位于同一個目錄下 
    InputStream in=
	this.getClass().getResourceAsStream("photo.gif");
    byte[] buffer = new byte[in.available()];
    in.read(buffer);
    customer.setImage(buffer);
    
//設置Customer對象的birthday屬性,
它是java.sql.Date類型
    customer.setBirthday
	(Date.valueOf("1980-05-06"));

    saveCustomer(customer);
    findAllCustomers(context,out);

    loadAndUpdateCustomer
	(customer.getId(),"Beijing");
    findAllCustomers(context,out);

  deleteAllCustomers();
  }

  public static void main(String args[])
  throws Exception
  {
    new BusinessService().test(null,System.out);
    sessionFactory.close();
  }
}

以上例子演示了通過Hibernate API訪問數據庫的一般流程。首先應該在應用的啟動階段對Hibernate進行初始化,然后就可以通過Hibernate的Session接口來訪問數據庫。 

2.5.1 Hibernate的初始化

BusinessService類的靜態(tài)代碼塊負責Hibernate的初始化工作,如讀取Hibernate的配置信息以及對象-關系映射信息,最后創(chuàng)建SessionFactory實例。當JVM(Java虛擬機)加載BusinessService類時,會執(zhí)行該靜態(tài)代碼塊。初始化過程包括如下步驟。

(1)創(chuàng)建一個Configuration類的實例,Configuration類的構造方法把默認文件路徑下的hibernate.properties配置文件中的配置信息讀入到內存:
Configuration config = new Configuration();


(2)調用Configuration類的addClass(Customer.class)方法:

config.addClass(Customer.class);

該方法把默認文件路徑下的Customer.hbm.xml文件中的映射信息讀入到內存中。

(3)調用Configuration類的buildSessionFactory()方法: 

sessionFactory = config.buildSessionFactory();

該方法創(chuàng)建一個SessionFactory實例,并把Configuration對象包含的所有配置信息拷貝到SessionFactory對象的緩存中。SessionFactory代表一個數據庫存儲源,如果應用只有一個數據庫存儲源,那么只需創(chuàng)建一個SessionFactory實例。當SessionFactory對象創(chuàng)建后,該對象不和Configuration對象關聯。

因此,如果再修改Configuration對象包含的配置信息,不會對SessionFactory對象有任何影響。由于Java語言是純面向對象的語言,因此不可能像C語言那樣直接操縱內存,例如聲明一段可用的內存空間。以上步驟(3)中提到了緩存的概念,這里的緩存其實指的是Java對象的屬性(通常是一些集合類型的屬性)占用的內存空間。

例如,SessionFactory的實現類中定義了許多集合類型的屬性,這些屬性用于存放Hibernate配置信息、映射元數據信息等:
public final class SessionFactoryImpl
implements SessionFactory, SessionFactoryImplementor
{
	private final transient Map classPersisters;
	private final transient Map classPersistersByName;
	private final transient Map classMetadata;
	private final transient Map collectionPersisters;
	private final transient Map collectionMetadata;
	private final transient Map namedQueries;
	private final transient Map namedSqlQueries;
	private final transient Map imports;
	private final transient Templates templates;
	private final transient Interceptor interceptor;
	private final transient Settings settings;
	private final transient Properties properties;
	private transient SchemaExport schemaExport;
	private final transient TransactionManager 
	transactionManager;
	private final transient QueryCache queryCache;
	private final transient UpdateTimestampsCache 
	updateTimestampsCache;
	private final transient Map queryCaches;
	……
}


如果對象的緩存很大,就稱為重量級對象。如果對象占用的內存空間很小,就稱為輕量級對象。SessionFactory就是個重量級對象,如果應用只有一個數據存儲源,只需創(chuàng)建一個SessionFactory實例,因為隨意地創(chuàng)建SessionFactory實例會占用大量內存空間。

SessionFactory的緩存可分為兩類:內置緩存和外置緩存。SessionFactory的內置緩存中存放了Hibernate配置信息和映射元數據信息等;SessionFactory的外置緩存是一個可配置的緩存插件,在默認情況下,SessionFactory不會啟用這個緩存插件。外置緩存能存放大量數據庫數據的拷貝,外置緩存的物理介質可以是內存或者硬盤。本書第13章(管理Hibernate的緩存)對此做了詳細介紹。

Hibernate的許多類和接口都支持方法鏈編程風格,Configuration類的addClass()方法返回當前Configuration實例,因此對于以下代碼:
Configuration config = new Configuration();
config.addClass(Customer.class);
sessionFactory = config.buildSessionFactory();

如果使用方法鏈編程風格,可以改寫為: 

sessionFactory = new Configuration()
.buildSessionFactory()
.addClass(Customer.class)
.buildSessionFactory();

方法鏈編程風格能使應用程序代碼更加簡捷。在使用這種編程風格時,最好把每個調用方法放在不同的行,否則在跟蹤程序時,無法跳入每個調用方法中。 

2.5.2 訪問Hibernate的Session接口

初始化過程結束后,就可以調用SessionFactory實例的openSession()方法來獲得Session實例,然后通過它執(zhí)行訪問數據庫的操作。Session接口提供了操縱數據庫的各種方法,如:

save()方法:把Java對象保存數據庫中。
update()方法:更新數據庫中的Java對象。
delete()方法:把Java對象從數據庫中刪除。
load()方法:從數據庫中加載Java對象。
find()方法:從數據庫中查詢Java對象。
Session是一個輕量級對象。通常將每一個Session實例和一個數據庫事務綁定,也就是說,每執(zhí)行一個數據庫事務,都應該先創(chuàng)建一個新的Session實例。如果事務執(zhí)行中出現異常,應該撤銷事務。不論事務執(zhí)行成功與否,最后都應該調用Session的close()方法,從而釋放Session實例占用的資源。以下代碼演示了用Session來執(zhí)行事務的流程,其中Transaction類用來控制事務。
Session session = factory.openSession();
  Transaction tx;
  try {
     //開始一個事務
     tx = session.beginTransaction();
     //執(zhí)行事務
     ...
     //提交事務
     tx.commit();
 }
 catch (Exception e) {
     //如果出現異常,就撤銷事務
     if (tx!=null) tx.rollback();
     throw e;
 }
 finally {
     //不管事務執(zhí)行成功與否,
	 最后都關閉Session
     session.close();
 }


圖2-6為正常執(zhí)行數據庫事務(即沒有發(fā)生異常)的時序圖。

 



圖2-6 正常執(zhí)行數據庫事務的時序圖

 


BusinessService類提供了保存、刪除、查詢和更新Customer對象的各種方法。BusinessService類的main()方法調用test()方法,test()方法又調用以下方法:

1.saveCustomer()方法

該方法調用Session的save()方法,把Customer對象持久化到數據庫中。
tx = session.beginTransaction();
  session.save(customer);
  tx.commit();

當運行session.save()方法時,Hibernate執(zhí)行以下SQL語句:

insert into CUSTOMERS 
(ID, NAME, EMAIL, PASSWORD, PHONE, ADDRESS, SEX,
IS_MARRIED,DESCRIPTION, IMAGE,
BIRTHDAY, REGISTERED_TIME) 
values(1,'Tom','tom@yahoo.com',
'1234',55556666,'Shanghai','M',0,
'I am very honest.', 
?,'1980-05-06',null)

在test()方法中并沒有設置Customer對象的id屬性,Hibernate會根據映射文件的配置,采用increment標識符生成器自動以遞增的方式為OID賦值。在Customer.hbm.xml文件中相關的映射代碼如下:

<id name="id" column="ID" type="long">
  <generator class="increment"/>
</id>

在test()方法中也沒有設置Customer對象的registeredTime屬性,因此在以上insert語句中,REGISTERED_TIME字段的值為null。但由于REGISTERED_TIME字段的SQL類型為TIMESTAMP類型,如果insert語句沒有為TIMESTAMP類型的字段賦值,底層數據庫會自動把當前的系統(tǒng)時間賦值給TIMESTAMP類型的字段。因此,執(zhí)行完以上insert語句后,REGISTERED_TIME字段的值并不為null,而是插入該記錄時的系統(tǒng)時間。 

2.findAllCustomers()方法

該方法調用Session的find()方法,查詢所有的Customer對象。 

tx = session.beginTransaction();
   List customers=session.find
   ("from Customer as c order by c.name asc");
   for (Iterator it = customers.iterator();
   it.hasNext();) {
      printCustomer(context,out,
	  (Customer) it.next());
   }
   tx.commit();

Session的find()方法有好幾種重載形式,本例中傳遞的是字符串參數"from Customer as c order by c.name asc",它使用的是Hibernate查詢語言。運行session.find()方法時,Hibernate執(zhí)行以下SQL語句: 

select * from CUSTOMERS order by NAME asc;

3.loadAndUpdateCustomer ()方法

該方法調用Session的load()方法,加載Customer對象,然后再修改Customer對象的屬性。

tx = session.beginTransaction();
Customer c=(Customer)session.load
(Customer.class,customer_id);
c.setAddress(address);
tx.commit();

以上代碼先調用Session的load()方法,它按照參數指定的OID從數據庫中檢索出匹配的Customer對象,Hibernate會執(zhí)行以下SQL語句: 

select * from CUSTOMERS where ID=1;

loadAndUpdateCustomer()方法接著修改Customer對象的address屬性。那么,Hibernate會不會同步更新數據庫中相應的CUSTOMERS表的記錄呢?答案是肯定的。Hibernate采用臟檢查機制,按照內存中的Customer對象的狀態(tài)的變化,來同步更新數據庫中相關的數據,Hibernate會執(zhí)行以下SQL語句: 

update CUSTOMERS set NAME="Tom",
EMAIL="Tom@yahoo.com"…ADDRESS="Beijing"…
where ID=1;

盡管只有Customer對象的address屬性發(fā)生了變化,但是Hibernate執(zhí)行的update語句中會包含所有的字段。在BusinessService類的test()方法中按如下方式調用loadAndUpdateCustomer ()方法:

saveCustomer(customer);
loadAndUpdateCustomer
(customer.getId(),"Beijing");

以上代碼并沒有直接給customer對象的id屬性賦值,當執(zhí)行saveCustomer(customer)方法時,Session的save()方法把customer對象持久化到數據庫中,并自動為id屬性賦值。

4.printCustomer()方法

該方法打印Customer對象的信息,它有三種重載形式。當helloapp應用作為獨立應用程序運行時,將調用printCustomer(PrintStream out,Customer customer)方法,向控制臺輸出Customer信息;當helloapp應用作為Java Web應用運行時,將調用printCustomer (ServletContext context,ServletOuputStream out,Customer customer)方法向Web客戶輸出Customer信息:
private void printCustomer
(ServletContext context,ServletOutputStream out,
Customer customer)throws Exception
{
//把Customer對象的image屬性包含的
二進制圖片數據保存到photo_copy.gif文件中
    byte[] buffer=customer.getImage();
    String path=context.getRealPath("/");
    FileOutputStream fout=
	new FileOutputStream(path+"photo_copy.gif");
    fout.write(buffer);
    fout.close();
out.println("------以下是"+customer.getName()+"
的個人信息------"+"<br>");
    out.println("ID: 
	"+customer.getId()+"<br>");
    out.println("口令: 
	"+customer.getPassword()+"<br>");
    out.println("E-Mail:
	"+customer.getEmail()+"<br>");
    out.println("電話:
	"+customer.getPhone()+"<br>");
    out.println("地址:
	"+customer.getAddress()+"<br>");
    String sex=customer.getSex()=='M'? "男":"女";
    out.println("性別:
	"+sex+"<br>");
    String marriedStatus=customer.isMarried()?
	"已婚":"未婚";
    out.println("婚姻狀況:
	"+marriedStatus+"<br>");
    out.println("生日: 
	"+customer.getBirthday()+"<br>");
    out.println("注冊時間:
	"+customer.getRegisteredTime()+"<br>");
    out.println("自我介紹: 
	"+customer.getDescription().
	substring(0,25)+"<br>");
    out.println("<img src='photo_copy.gif'
	border=0><p>");
	//顯示photo_copy.gif圖片
}

5.deleteAllCustomers()方法

該方法調用Session的delete()方法,刪除所有的Customer對象:

tx = session.beginTransaction();
session.delete("from Customer as c");
tx.commit();

Session的delete()方法有好幾種重載形式,本例向delete()方法提供了字符串參數"from Customer as c",它使用的是Hibernate查詢語言(HQL,Hibernate Query Language)。HQL是一種面向對象的語言,"from Customer as c"字符串指定的是Customer類的名字,而非CUSTOMERS表的名字,其中"as c"表示為Customer類賦予別名"c"。

運行session.delete()方法時,Hibernate先執(zhí)行select語句,查詢CUSTOMERS表的所有Customer對象: 

select * from CUSTOMERS;

接下來Hibernate根據Customer對象的OID,依次刪除每個對象: 

delete from CUSTOMERS where ID=1;
作者:mixer_a 發(fā)表于2012-4-10 22:34:33 原文鏈接
閱讀:4 評論:0 查看評論