UML软件工程组织

Java对象持久化技术之Hibernate入门之三
来源:赛迪网 作者:孙卫琴
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节提到Hibernate没有渗透到域模型中,即在持久化类中没有引入任何Hibernate API。但是对于应用中负责处理业务的过程域对象,当然应该借助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,创建SessionFactory实例 */
  static{
    try{
      // 根据默认位置的Hibernate配置文件的配置信息,
	  创建一个Configuration实例
      Configuration config = new Configuration();
      config.addClass(Customer.class);
      
// 创建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();
    }
  }
 
  /** 选择向控制台还是动态网页
  输出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对象的信息输出到动态网页 */
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属性,
它是字节数组,存放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类的静态代码块负责Hibernate的初始化工作,如读取Hibernate的配置信息以及对象-关系映射信息,最后创建SessionFactory实例。当JVM(Java虚拟机)加载BusinessService类时,会执行该静态代码块。初始化过程包括如下步骤。

(1)创建一个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();

该方法创建一个SessionFactory实例,并把Configuration对象包含的所有配置信息拷贝到SessionFactory对象的缓存中。SessionFactory代表一个数据库存储源,如果应用只有一个数据库存储源,那么只需创建一个SessionFactory实例。当SessionFactory对象创建后,该对象不和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就是个重量级对象,如果应用只有一个数据存储源,只需创建一个SessionFactory实例,因为随意地创建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实例,然后通过它执行访问数据库的操作。Session接口提供了操纵数据库的各种方法,如:

save()方法:把Java对象保存数据库中。
update()方法:更新数据库中的Java对象。
delete()方法:把Java对象从数据库中删除。
load()方法:从数据库中加载Java对象。
find()方法:从数据库中查询Java对象。
Session是一个轻量级对象。通常将每一个Session实例和一个数据库事务绑定,也就是说,每执行一个数据库事务,都应该先创建一个新的Session实例。如果事务执行中出现异常,应该撤销事务。不论事务执行成功与否,最后都应该调用Session的close()方法,从而释放Session实例占用的资源。以下代码演示了用Session来执行事务的流程,其中Transaction类用来控制事务。

Session session = factory.openSession();
  Transaction tx;
  try {
     //开始一个事务
     tx = session.beginTransaction();
     //执行事务
     ...
     //提交事务
     tx.commit();
 }
 catch (Exception e) {
     //如果出现异常,就撤销事务
     if (tx!=null) tx.rollback();
     throw e;
 }
 finally {
     //不管事务执行成功与否,
	 最后都关闭Session
     session.close();
 }


图2-6为正常执行数据库事务(即没有发生异常)的时序图。



图2-6 正常执行数据库事务的时序图


BusinessService类提供了保存、删除、查询和更新Customer对象的各种方法。BusinessService类的main()方法调用test()方法,test()方法又调用以下方法:

1.saveCustomer()方法

该方法调用Session的save()方法,把Customer对象持久化到数据库中。

tx = session.beginTransaction();
  session.save(customer);
  tx.commit();

当运行session.save()方法时,Hibernate执行以下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类型的字段赋值,底层数据库会自动把当前的系统时间赋值给TIMESTAMP类型的字段。因此,执行完以上insert语句后,REGISTERED_TIME字段的值并不为null,而是插入该记录时的系统时间。

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执行以下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会执行以下SQL语句:

select * from CUSTOMERS where ID=1;

loadAndUpdateCustomer()方法接着修改Customer对象的address属性。那么,Hibernate会不会同步更新数据库中相应的CUSTOMERS表的记录呢?答案是肯定的。Hibernate采用脏检查机制,按照内存中的Customer对象的状态的变化,来同步更新数据库中相关的数据,Hibernate会执行以下SQL语句:

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

尽管只有Customer对象的address属性发生了变化,但是Hibernate执行的update语句中会包含所有的字段。在BusinessService类的test()方法中按如下方式调用loadAndUpdateCustomer ()方法:

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

以上代码并没有直接给customer对象的id属性赋值,当执行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先执行select语句,查询CUSTOMERS表的所有Customer对象:

select * from CUSTOMERS;

接下来Hibernate根据Customer对象的OID,依次删除每个对象:

delete from CUSTOMERS where ID=1;

 

 

版权所有:UML软件工程组织