Hibernate详细讲解

来源:互联网 发布:可以自己做软件 编辑:程序博客网 时间:2024/06/05 06:49

 Hibernate是Java应用和关系数据库之间的桥梁,它负责Java对象和关系数据之间的映射。Hibernate内部封装了通过JDBC访问数据库的操作,向上层应用提供了面向对象的数据访问API。在Java应用中使用Hibernate包含以下步骤。

(1)创建Hibernate的配置文件。

(2)创建持久化类。

(3)创建对象-关系映射文件。

(4)通过Hibernate API编写访问数据库的代码。

本章通过一个简单的例子helloapp应用,演示如何运用Hibernate来访问关系数据库。helloapp应用的功能非常简单:通过Hibernate保存、更新、删除、加载及查询Customer对象。图2-1显示了Hibernate在helloapp应用中所处的位置。
helloapp应用既能作为独立的Java程序运行,还能作为Java Web应用运行,该应用的源代码位于配套光盘的sourcecode/chapter2/helloapp目录下。

2.1 创建Hibernate的配置文件

Hibernate从其配置文件中读取和数据库连接有关的信息,这个配置文件应该位于应用的classpath中。Hibernate的配置文件有两种形式:一种是XML格式的文件;还有一种是Java属性文件,采用“健=值”的形式。

下面介绍如何以Java属性文件的格式来创建Hibernate的配置文件。这种配置文件的默认文件名为hibernate.properties,例程2-1为示范代码。

例程2-1 hibernate.properties

hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect

hibernate.connection.driver_class=com.mysql.jdbc.Driver

hibernate.connection.url=jdbc:mysql://localhost:3306/SAMPLEDB

hibernate.connection.username=root

hibernate.connection.password=1234

hibernate.show_sql=true

以上hibernate.properties文件包含了一系列属性及其属性值,Hibernate将根据这些属性来连接数据库,本例为连接MySQL数据库的配置代码。表2-1对以上hibernate.properties文件中的所有属性做了描述。

表2-1 Hibernate配置文件的属性

属 性


描 述

hibernate.dialect


指定数据库使用的SQL方言

hibernate.connection.driver_class


指定数据库的驱动程序

hibernate.connection.url


指定连接数据库的URL

hibernate.connection.username


指定连接数据库的用户名

hibernate.connection.password


指定连接数据库的口令

hibernate.show_sql


如果为true,表示在程序运行时,会在控制台输出SQL语句,这有利于跟踪Hibernate的运行状态,默认为false。在应用开发和测试阶段,可以把这个属性设为true,以便跟踪和调试应用程序,在应用发布阶段,应该把这个属性设为false,以便减少应用的输出信息,提高运行性能

Hibernate能够访问多种关系数据库,如MySQL、Oracle和Sybase等。尽管多数关系数据库都支持标准的SQL语言,但是它们往往还有各自的SQL方言,就像不同地区的人既能说标准的普通话,还能讲各自的方言一样。hibernate.dialect属性用于指定被访问数据库使用的SQL方言,当Hibernate生成SQL查询语句,或者使用native对象标识符生成策略时,都会参考本地数据库的SQL方言。本书第5章(映射对象标识符)介绍了Hibernate的各种对象标识符生成策略。

 

 

 

在Hibernate软件包的etc目录下,有一个hibernate.properties文件,它提供了连接各种关系数据库的配置代码样例。
2.2 创建持久化类

持久化类是指其实例需要被Hibernate持久化到数据库中的类。持久化类通常都是域模型中的实体域类。持久化类符合JavaBean的规范,包含一些属性,以及与之对应的getXXX()和setXXX()方法。例程2-2定义了一个名为Customer的持久化类。

例程2-2 Customer.java

package mypack;

import java.io.Serializable;

import java.sql.Date;

import java.sql.Timestamp;

 

public class Customer implements Serializable{

private Long id;

private String name;

private String email;

private String password;

private int phone;

private boolean married;

private String address;

private char sex;

private String description;

private byte[] image;

private Date birthday;

private Timestamp registeredTime;

 

public Customer(){}

 

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;

}

 

//此处省略email、password和phone等属性的getXXX()和setXXX()方法

……

}

持久化类符合JavaBean的规范,包含一些属性,以及与之对应的getXXX()和setXXX()方法。getXXX()和setXXX()方法必须符合特定的命名规则,“get”和“set”后面紧跟属性的名字,并且属性名的首字母为大写,例如name属性的get方法为getName(),如果把get方法写为getname()或者getNAME(),会导致Hibernate在运行时抛出以下异常:

net.sf.hibernate.PropertyNotFoundException: Could not find a getter

for property name in class mypack.Customer

如果持久化类的属性为boolean类型,那么它的get方法名既可以用“get”作为前缀,也可以用“is”作为前缀。例如Customer类的married属性为boolean类型,因此以下两种get方法是等价的:

public boolean isMarried(){

return married;

}

或者:

public boolean getMarried(){

return married;

}

Hibernate并不要求持久化类必须实现java.io.Serializable接口,但是对于采用分布式结构的Java应用,当Java对象在不同的进程节点之间传输时,这个对象所属的类必须实现Serializable接口,此外,在Java Web应用中,如果希望对HttpSession中存放的Java对象进行持久化,那么这个Java对象所属的类也必须实现Serializable接口。

Customer持久化类有一个id属性,用来惟一标识Customer类的每个对象。在面向对象术语中,这个id属性被称为对象标识符(OID,Object Identifier),通常它都用整数表示,当然也可以设为其他类型。如果customerA.getId().equals(customerB.getId())的结果是true,就表示customerA和customerB对象指的是同一个客户,它们和CUSTOMERS表中的同一条记录对应。

Hibernate要求持久化类必须提供一个不带参数的默认构造方法,在程序运行时,Hibernate运用Java反射机制,调用java.lang.reflect.Constructor.newInstance()方法来构造持久化类的实例。如果对这个持久化类使用延迟检索策略,为了使Hibernate能够在运行时为这个持久化类创建动态代理,要求持久化类的默认构造方法的访问级别必须是public或protected类型,而不能是default或private类型。在本书第10章(Hibernate的检索策略)介绍了Hibernate的延迟检索策略及动态代理的概念。

在Customer类中没有引入任何Hibernate API,Customer类不需要继承Hibernate的类,或实现Hibernate的接口,这提高了持久化类的独立性。如果日后要改用其他的ORM产品,比如由Hibernate改为OJB,不需要修改持久化类的代码。

本书第1章介绍了J2EE的持久化方案,无论是基于CMP的实体EJB,还是基于BMP的实体EJB,它们的共同特点是都必须运行在EJB容器中。而Hibernate支持的持久化类不过是普通的Java类,它们能够运行在任何一种Java环境中。
2.3 创建数据库Schema

在本例中,与Customer类对应的数据库表名为CUSTOMERS,它在MySQL数据库中的DDL定义如下:

create table CUSTOMERS (

ID bigint not null primary key,

NAME varchar(15) not null,

EMAIL varchar(128) not null,

PASSWORD varchar(8) not null,

PHONE int ,

ADDRESS varchar(255),

SEX char(1) ,

IS_MARRIED bit,

DESCRIPTION text,

IMAGE blob,

BIRTHDAY date,

REGISTERED_TIME timestamp

);

CUSTOMERS表有一个ID字段,它是表的主键,它和Customer类的id属性对应。CUSTOMERS表中的字段使用了各种各样的SQL类型,参见表2-2。

表2-2 CUSTOMERS表的字段使用的SQL类型

字 段 名


SQL类型


说 明

ID


BIGINT


整数,占8字节,取值范围为:-2^63 ~ 2^63 - 1

NAME


VARCHAR


变长字符串,占0 ~ 255字节

SEX


CHAR


定长字符串,占0 ~ 255字节

IS_MARRIED


BIT


布尔类型

DESCRIPTION


TEXT


长文本数据,占0 ~ 65 535字节。如果字符串长度小于255,可以用VARCHAR或CHAR类型来表示。如果字符串长度大于255,可以定义为TEXT类型

 

(续表)

字 段 名


SQL类型


说 明

IMAGE


BLOB


二进制长数据,占0 ~ 65 535字节,BLOB是Binary Large Object的缩写。在本例中,IMAGE字段用来存放图片数据

BIRTHDAY


DATE


代表日期,格式为“YYYY-MM-DD”

REGISTERED_TIME


TIMESTAMP


代表日期和时间,格式为“YYYYMMDDHHMMSS”
2.4 创建对象-关系映射文件

Hibernate采用XML格式的文件来指定对象和关系数据之间的映射。在运行时,Hibernate将根据这个映射文件来生成各种SQL语句。在本例中,将创建一个名为Customer.hbm.xml的文件,它用于把Customer类映射到CUSTOMERS表,这个文件应该和Customer.class文件存放在同一个目录下。例程2-3为Customer.hbm.xml文件的代码。

例程2-3 Customer.hbm.xml

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

 

<hibernate-mapping>

<class name="mypack.Customer" table="CUSTOMERS">

 

<id name="id" column="ID" type="long">

<generator class="increment"/>

</id>

 

<property name="name" column="NAME" type="string" not-null="true" />

<property name="email" column="EMAIL" type="string" not-null="true" />

<property name="password" column="PASSWORD" type="string" not-null="true"/>

<property name="phone" column="PHONE" type="int" />

<property name="address" column="ADDRESS" type="string" />

<property name="sex" column="SEX" type="character"/>

<property name="married" column="IS_MARRIED" type="boolean"/>

<property name="description" column="DESCRIPTION" type="text"/>

<property name="image" column="IMAGE" type="binary"/>

<property name="birthday" column="BIRTHDAY" type="date"/>

<property name="registeredTime" column="REGISTERED_TIME" type="timestamp"/>

 

</class>

</hibernate-mapping>

2.4.1 映射文件的文档类型定义(DTD)

在例程2-3的Customer.hbm.xml文件的开头声明了DTD(Document Type Definition,文档类型定义),它对XML文件的语法和格式做了定义。Hibernate的XML解析器将根据DTD来核对XML文件的语法。

每一种XML文件都有独自的DTD文件。Hibernate的对象-关系映射文件使用的DTD文件的下载网址为:http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd。此外,在Hibernate软件包的src/net/sf/hibernate目录下也提供了hibernate-mapping-2.0.dtd文件。在这个文件中,描述顶层元素<hibernate-mapping>的代码如下:

<!ELEMENT hibernate-mapping (meta*, import*, (class|subclass|joined-subclass)*, query*,

sql-query*)>

描述顶层元素<hibernate-mapping>的子元素<class>的代码如下:

<!ELEMENT class (

meta*,

(cache|jcs-cache)?,

(id|composite-id),

discriminator?,

(version|timestamp)?,

(property|many-to-one|one-to-one|component|dynamic-component|any|map|set|list|bag|idbag|array

|primitive-array)*,

((subclass*)|(joined-subclass*))

)>

<hibernate-mapping>元素是对象-关系映射文件的根元素,其他元素(即以上DTD代码中括号以内的元素,如<class>子元素)必须嵌入在<hibernate-mapping>元素以内。在<class>元素中又嵌套了好多子元素。在以上DTD代码中,还使用了一系列的特殊符号来修饰元素,表2-3描述了这些符号的作用。在创建自己的对象-关系映射文件时,如果不熟悉某种元素的语法,可以参考DTD文件。

表2-3 DTD中特殊符号的作用

符 号


含 义

无符号


该子元素在父元素内必须存在且只能存在一次

+


该子元素在父元素内必须存在,可以存在一次或者多次

*


该子元素在父元素内可以不存在,或者存在一次或者多次,它是比较常用的符号

?


该子元素在父元素内可以不存在,或者只存在一次,它是比较常用的符号

根据表2-3可以看出,在<hibernate-mapping>元素中,<meta>、<import>、<class>和<query>等子元素可以不存在,或者存在一次或者多次;在<class>元素中,<id>子元素必须存在且只能存在一次,<property>元素可以不存在,或者存在一次或者多次。

此外,在映射文件中,父元素中的各种子元素的定义必须符合特定的顺序。例如,根据<class>元素的DTD可以看出,必须先定义<id>子元素,再定义<property>子元素,以下映射代码颠倒了<id>和<property>子元素的位置:

<class name="mypack.Customer" table="CUSTOMERS">

<property name="name" column="NAME" type="string" not-null="true" />

<property name="email" column="EMAIL" type="string" not-null="true" />

 

<id name="id" column="ID" type="long">

<generator class="increment"/>

</id>

……

</class>

Hibernate的XML解析器在运行时会抛出MappingException:

[java] 21:27:51,610 ERROR XMLHelper:48 - Error parsing XML:

XML InputStream (24) The content of element type "class" must match "(meta*,(cache|jcs-cache)?,(

id|composite-id),discriminator?,(version|timestamp)?,(property|many-to-one|one-to-one|component|

dynamic-component|any|map|set|list|bag|idbag|array|primitive-array)*,(subclass*|joined-subclass*))".

 

[java] net.sf.hibernate.MappingException: Error reading resource: mypack/Customer.hbm.xml

at net.sf.hibernate.cfg.Configuration.addClass(Configuration.java:357)
2.4.2 把Customer持久化类映射到CUSTOMERS表

例程2-3的Customer.hbm.xml文件用于映射Customer类。如果需要映射多个持久化类,那么既可以在同一个映射文件中映射所有类,也可以为每个类创建单独的映射文件,映射文件和类同名,扩展名为“hbm.xml”。后一种做法更值得推荐,因为在团队开发中,这有利于管理和维护映射文件。

<class>元素指定类和表的映射,它的name属性设定类名,table属性设定表名。以下代码表明和Customer类对应的表为CUSTOMERS表:

<class name="mypack.Customer" table="CUSTOMERS">

如果没有设置<class>元素的table属性,Hibernate将直接以类名作为表名,也就是说,在默认情况下,与mypack.Customer类对应的表为Customer表。

<class>元素包含一个<id>子元素及多个<property>子元素。<id>子元素设定持久化类的OID和表的主键的映射。以下代码表明Customer类的id属性和CUSTOMERS表中的ID字段对应。

<id name="id" column="ID" type="long">

<generator class="increment"/>

</id>

<id>元素的<generator>子元素指定对象标识符生成器,它负责为OID生成惟一标识符。本书第5章(映射对象标识符)详细介绍了Hibernate提供的各种对象标识符生成器的用法。

<property>子元素设定类的属性和表的字段的映射。<property>子元素主要包括name、type、column和not-null属性。
1.<property>元素的name属性

<property>元素的name属性指定持久化类的属性的名字。
2.<property>元素的type属性

<property>元素的type属性指定Hibernate映射类型。Hibernate映射类型是Java类型与SQL类型的桥梁。表2-4列出了Customer类的属性的Java类型、Hibernate映射类型,以及CUSTOMERS表的字段的SQL类型这三者之间的对应关系。

表2-4 Java类型、Hibernate映射类型以及SQL类型之间的对应关系

Customer类的属性


Java类型


Hibernate映射类型


CUSTOMERS表的字段


SQL类型

name


java.lang.String


string


NAME


VARCHAR(15)

email


java.lang.String


string


EMAIL


VARCHAR(128)

password


java.lang.String


string


PASSWORD


VARCHAR(8)

phone


int


int


PHONE


INT

address


java.lang.String


string


ADDRESS


VARCHAR(255)

sex


char


character


SEX


CHAR(1)

married


boolean


boolean


IS_MARRIED


BIT

description


java.lang.String


text


DESCRIPTION


TEXT

image


byte[]


binary


IMAGE


BLOB

birthday


java.sql.Date


date


BIRTHDAY


DATE

registeredTime


java.sql.Timestamp


timestamp


REGISTERED_TIME


TIMESTAMP

从表2-4看出,如果Customer类的属性为java.lang.String类型,并且与此对应的CUSTOMERS表的字段为VARCHAR类型,那么应该把Hibernate映射类型设为string,例如:

<property name="name" column="NAME" type="string" not-null="true" />

如果Customer类的属性为java.lang.String类型,并且与此对应的CUSTOMERS表的字段为TEXT类型,那么应该把Hibernate映射类型设为text,例如:

<property name="description" column="DESCRIPTION" type="text"/>

如果Customer类的属性为byte[]类型,并且与此对应的CUSTOMERS表的字段为BLOB类型,那么应该把Hibernate映射类型设为binary,例如:

<property name="image" column="IMAGE" type="binary"/>

如果没有显式设定映射类型,Hibernate会运用Java反射机制先识别出持久化类的属性的Java类型,然后自动使用与之对应的默认的Hibernate映射类型。例如,Customer类的address属性为java.lang.String类型,与java.lang.String对应的默认的映射类型为string,因此以下两种设置方式是等价的:

<property name="address" column="ADDRESS"/>

或者:

<property name="address" type="string" />

对于Customer类的description属性,尽管它是java.lang.String类型,由于CUSTOMERS表的DESCRIPTION字段为text类型,因此必须显式地把映射类型设为text。
3.<property>元素的not-null属性

如果<property>元素的not-null属性为true,表明不允许为null,默认为false。例如以下代码表明不允许Customer类的name属性为null:

<property name="name" column="NAME" type="string" not-null="true" />

Hibernate在持久化一个Customer对象时,会先检查它的name属性是否为null,如果为null,就会抛出以下异常:

net.sf.hibernate.PropertyValueException: not-null property references

a null or transient value: mypack.Customer.name

如果数据库中CUSTOMERS表的NAME字段不允许为null,但在映射文件中没有设置not-null属性:

<property name="name" column="NAME" type="string" />

那么Hibernate在持久化一个Customer对象时,不会先检查它的name属性是否为null,而是直接通过JDBC API向CUSTOMERS表插入相应的数据,由于CUSTOMERS表的NAME字段设置了not null约束,因此数据库会抛出错误:

708 ERROR JDBCExceptionReporter:58 - General error, message from server:

"Column 'NAME' cannot be null"

值得注意的是,对于实际Java应用,当持久化一个Java对象时,不应该依赖Hibernate或数据库来负责数据验证。在四层应用结构中,应该由表述层或者业务逻辑层负责数据验证。例如对于Customer对象的name属性,事实上在表述层就能检查name属性是否为null,假如表述层、业务逻辑层和Hibernate持久化层都没有检查name属性是否为null,那么数据库层会监测到NAME字段违反了数据完整性约束,从而抛出异常,如图2-2所示,包含非法数据的Customer对象从表述层依次传到数据库层,随后从数据库层抛出的错误信息又依次传到表述层,这种做法显然会降低数据验证的效率。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


图2-2 由数据库层负责数据验证

既然如此,把<property>元素的not-null属性设为true,有何意义呢?这主要是便于在软件开发和测试阶段能捕获表述层或者业务逻辑层应该处理而未处理的异常,提醒开发人员在表述层或者业务逻辑层中加入必要的数据验证逻辑。
4.<property>元素的column属性

<property>元素的column属性指定与类的属性映射的表的字段名。以下代码表明和address属性对应的字段为ADDRESS字段:

<property name="address" column= "ADDRESS" type="string"/>

如果没有设置< property >元素的column属性,Hibernate将直接以类的属性名作为字段名,也就是说,在默认情况下,与Customer类的address属性对应的字段为address字段。

<property>元素还可以包括<column>子元素,它和<property>元素的column属性一样,都可以设定与类的属性映射的表的字段名。以下两种设置方式是等价的:

<property name="address" column= "ADDRESS" type="string"/>

或者:

<property name="address" type="string">

<column name="ADDRESS" />

</property>

<property>元素的<column>子元素比column属性提供更多的功能,它可以更加详细地描述表的字段。例如,以下<column>子元素指定CUSTOMERS表中的NAME字段的SQL类型为varchar(15),不允许为null,并且为这个字段建立了索引:

<property name="name" type="string">

<column name="NAME" sql-type="varchar(15)" not-null="true" index="idx_name" />

</property>

<column>子元素主要和hbm2ddl工具联合使用。当使用hbm2ddl工具来自动生成数据库Schema时,hbm2ddl工具将依据<column>子元素提供的信息来定义表的字段。关于hbm2ddl工具的用法参见本书第3章(hbm2java和hbm2ddl工具)。如果数据库Schema是通过手工方式创建的,就不必通过<column>子元素设定字段的详细信息,通常只需设定它的name属性和not-null属性就可以了,例如:

<property name="name" type="string">

<column name="NAME" not-null="true" />

</property>

或者:

<property name="name" column="NAME" type="string" not-null="true" />

除了not-null属性以外,<column>子元素的多数属性(如sql-type或index属性)都不会影响Hibernate的运行时行为。

图2-3显示了Customer.hbm.xml配置的对象-关系映射。

 

 

 

 

 

 


图2-3 Customer.hbm.xml配置的映射

Hibernate采用XML文件来配置对象-关系映射,有以下优点:

l Hibernate既不会渗透到上层域模型中,也不会渗透到下层数据模型中。

l 软件开发人员可以独立设计域模型,不必强迫遵守任何规范。

l 数据库设计人员可以独立设计数据模型,不必强迫遵守任何规范。

l 对象-关系映射不依赖于任何程序代码,如果需要修改对象-关系映射,只需修改XML文件,不需要修改任何程序,提高了软件的灵活性,并且使维护更加方便。
2.5 通过Hibernate API操纵数据库

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接口提供了操纵数据库的各种方法,如:

l save()方法:把Java对象保存数据库中。

l update()方法:更新数据库中的Java对象。

l delete()方法:把Java对象从数据库中删除。

l load()方法:从数据库中加载Java对象。

l 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;
2.6 运行helloapp应用

helloapp应用既能作为独立的Java程序来运行,还能作为Java Web应用来运行。当作为独立应用程序,将直接运行BusinessService类的main()方法;当作为Java Web应用,需要在Java Web服务器上才能运行,本书采用Tomcat服务器。
2.6.1 创建运行本书范例的系统环境

运行本书的例子,需要安装以下软件。

(1)JDK1.4:Hibernate要求JDK1.4以上的版本,如果安装JDK1.3,可能会导致程序无法正常编译或运行。

(2)ANT:它是发布、编译和运行Java应用的工具软件。

(3)Hibernate: 它是本书介绍的ORM工具软件。

(4)MySQL:它是本书范例使用的关系数据库。

(5)Tomcat:它是本书范例使用的Java Web服务器,在本章以及第19章(Hibernate与Struts框架)需要用到它。

(6)JBoss:它是本书范例使用的J2EE应用服务器。在第20章(Hibernate与EJB组件)需要用到它。

表2-5列出了以上软件的下载网址。此外,本书配套光盘的software目录下也提供了以上软件(不包括JDK软件)。

表2-5 本书使用的软件的下载网址

软 件


下载网地址

JDK1.4


www.sun.com

ANT


jakarta.apache.org

Tomcat


jakarta.apache.org

MySQL


www.mysql.com

Jboss


www.jboss.org

Hibernate


www.hibernate.org

以上软件的安装都很简单,其中JDK和MySQL的安装软件是可运行程序,只需直接运行安装程序。对于MySQL5.x版本,在安装MySQL的过程中,会出现为“root”用户输入口令的窗口,在该窗口中设置口令为“1234”,如图2-7所示。在本书中,登入MySQL服务器的用户名一律为“root”,口令为“1234”。

图2-7 设置MySQL的“root”用户的口令

 

MySQL目前最成熟的版本为4.1,但是该版本不支持SQL子查询语句,MySQL的最新版本5.0提供了这一功能,不过该版本还处于测试阶段。本书配套光盘提供了MySQL5.0的测试版本,选用这个版本,是为了便于演示Hibernate的各种高级功能。

Tomcat、Jboss、ANT和Hibernate的安装软件是压缩软件包,只需把压缩文件解压到本地硬盘。安装好以上软件后需要在操作系统中设置以下环境变量:

l JAVA_HOME:JDK的安装目录。

l ANT_HOME:ANT的安装目录。

l CATALINA_HOME:Tomcat的安装目录。

l PATH: 把%JAVA_HOME%/bin目录添加到PATH变量中,以便于在当前路径下,从DOS命令行直接运行JDK的javac和java命令;把%ANT_HOME%/bin目录添加到PATH变量中,以便于在当前路径下,从DOS命令行直接运行ANT。

JAVA_HOME和ANT_HOME环境变量是必须设置的,而CATALINA_HOME和PATH环境变量不是必须设置的。
1.启动Tomcat服务器

运行<CATALINA_HOME>/bin/startup.bat,就会启动Tomcat服务器,然后通过浏览器访问http://localhost:8080/,如果Tomcat启动成功,可看到Tomcat的主页。
2.启动MySQL服务器

MySQL服务器既可以作为前台服务程序运行,也可以作为后台服务程序运行。在MySQL安装目录的bin目录下提供了以下MySQL服务器程序:

l mysqld.exe:最基本的MySQL服务器程序。

l mysqld-nt.exe:Windows NT/2000/XP平台的优化版本,支持命名管道。

执行以上任意一个程序,都会启动MySQL服务,它以前台服务的形式运行。此外,也可以按以下步骤把MySQL作为后台服务运行:

(1)在DOS下转到MySQL的安装目录的bin子目录下。

(2)在NT/Windows 2000中注册MySQL服务,输入命令:mysqld-nt --install。

(3)启动MySQL服务,输入命令:net start mysql。

如果要停止MySQL服务,命令为:net stop mysql。如果要从Windows NT/Windows 2000中删除MySQL服务,命令为:mysqld-nt --remove。

此外,也可以通过Windows NT/Windows 2000的【控制面板】→【管理工具】→【服务】程序来管理注册过的MySQL服务。
3.运行MySQL的客户程序

(1)在Windows NT/Windows 2000下,执行【开始】→【程序】→【MySQL】→【MySQL Server 5.0】→【MySQL CommandLine Client】菜单,就会运行MySQL的客户程序mysql.exe,参见图2-8,该客户程序先提示输入root用户的口令,此处应该输入口令“1234”,即root用户的口令。此外,也可以直接在DOS命令行下运行mysql.exe程序,步骤为先转到MySQL安装目录的bin目录下,输入如下命令:

C:/mysql/bin> mysql –u root -p

接下来会提示输入root用户的口令,此处输入口令“1234”:

Enter password: ****

然后就会进入图2-8所示的命令行客户程序。

(2)创建数据库SAMPLEDB,SQL命令如下:

create database SAMPLEDB;

(3)进入SAMPLEDB数据库,SQL命令如下:

use SAMPLEDB;

(4)在SAMPLEDB数据库中创建CUSTOMERS表,SQL命令如下:

create table CUSTOMERS (

ID bigint not null primary key,

NAME varchar(15) not null,

EMAIL varchar(128) not null,

PASSWORD varchar(8) not null,

PHONE int ,

ADDRESS varchar(255),

SEX char(1) ,

IS_MARRIED bit,

DESCRIPTION text,

IMAGE blob,

BIRTHDAY date,

REGISTERED_TIME timestamp

);

(5)退出MySQL客户程序,输入命令:exit。

图2-8 MySQL的客户程序

在配套光盘的sourcecode/chapter2/helloapp/schema目录下提供了创建SAMPLEDB数据库和CUSTOMERS表的SQL脚本文件,文件名为sampledb.sql。如果不想在MySQL.exe程序中手工输入SQL语句,也可以直接运行sampledb.sql,步骤为先转到MySQL安装目录的bin目录下,输入如下命令:

C:/mysql/bin> mysql –u root –p <C:/helloapp/schema/sampledb.sql

接下来会提示输入root用户的口令,此处输入口令“1234”:

Enter password: ****

接下来MySQL客户程序就会自动执行C:/helloapp/schema/sampledb.sql文件中的所有SQL语句。在以上mysql命令中,“<”后面设定SQL脚本文件的路径。
2.6.2 创建helloapp应用的目录结构

本书提供的helloapp应用采用Java Web应用的标准目录结构。当应用中使用了Hibernate,要求在其目录结构中添加如下文件:

(1)在WEB-INF/classes目录下添加Hibernate的配置文件,如hibernate.properties文件。

(2)在WEB-INF/classes目录下添加对象-关系映射文件,在默认情况下,存放位置和相应的类文件在同一个位置。例如在本例中,Customer.class和Customer.hbm.xml均位于WEB-INF/classes/mypack目录下。

(3)在WEB-INF/lib目录下添加数据库的JDBC驱动程序JAR文件。

(4)在WEB-INF/lib目录下添加Hibernate所需的JAR文件。具体做法是:把Hibernate根目录下的hibernate2.jar文件及其lib子目录下的所有JAR文件拷贝到helloapp应用的WEB-INF/lib目录下。

图2-9显示了在编译helloapp应用之前的初始目录结构。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

图2-9 Helloapp应用的初始目录结构

 

 

 

在图2-9的src目录下还有一个log4j.properties文件,它是Log4J的配置文件。Log4j是由Apache开放源代码组织开发的日志生成器,它的网址为:http://logging.apache.org/log4j/docs/index.html。在Hibernate中集成了Log4J软件,用它来输出日志。

src子目录用于存放Java源文件、hibernate.properties以及Customer.hbm.xml文件。接下来采用ANT工具创建classes子目录,把hibernate.properties和Customer.hbm.xml文件拷贝到classes目录下,编译生成的类文件也放在classes目录下。
2.6.3 把helloapp应用作为独立应用程序运行

在helloapp应用的根目录下有一个build.xml文件,它是ANT的工程文件,参见例程2-5。

例程2-5 build.xml

<?xml version="1.0"?>

<project name="Learning Hibernate" default="prepare" basedir=".">

 

<!-- Set up properties containing important project directories -->

<property name="source.root" value="src"/>

<property name="class.root" value="WEB-INF/classes"/>

<property name="lib.dir" value="WEB-INF/lib"/>

 

<!-- Set up the class path for compilation and execution -->

<path id="project.class.path">

<!-- Include our own classes, of course -->

<pathelement location="${class.root}" />

<!-- Include jars in the project library directory -->

<fileset dir="${lib.dir}">

<include name="*.jar"/>

</fileset>

</path>

 

<!-- Create our runtime subdirectories and copy resources into them -->

<target name="prepare" description="Sets up build structures">

<delete dir="${class.root}"/>

<mkdir dir="${class.root}"/>

 

<!-- Copy our property files and O/R mappings for use at runtime -->

<copy todir="${class.root}" >

<fileset dir="${source.root}" >

<include name="**/*.properties"/>

<include name="**/*.hbm.xml"/>

<include name="**/*.xml"/>

<include name="**/*.gif"/>

</fileset>

</copy>

</target>

 

<!-- Compile the java source of the project -->

<target name="compile" depends="prepare"

description="Compiles all Java classes">

<javac srcdir="${source.root}"

destdir="${class.root}"

debug="on"

optimize="off"

deprecation="on">

<classpath refid="project.class.path"/>

</javac>

</target>

 

<target name="run" description="Run a Hibernate sample"

depends="compile">

<java classname="mypack.BusinessService" fork="true">

<classpath refid="project.class.path"/>

</java>

</target>

 

</project>

在build.xml文件中先定义了三个属性:

<property name="source.root" value="src"/>

<property name="class.root" value="WEB-INF/classes"/>

<property name="lib.dir" value="WEB-INF/lib"/>

source.root属性指定Java源文件的路径,class.root属性指定Java类的路径,lib.dir属性指定所有JAR文件的路径。

在build.xml文件中接着定义了三个target。

l prepare target:如果存在classes子目录,先将它删除。接着重新创建classes子目录。然后把src子目录下所有扩展名为“.properties”、“.hbm.xml”、“.xml”以及“.gif”的文件拷贝到WEB-INF/classes目录下。

l compile target:编译src子目录下的所有Java源文件。编译生成的类文件存放在WEB-INF/classes子目录下。

l run target:运行BusinessService类。

以上三个target的依赖关系参见图2-10。所谓依赖,是指在执行当前target之前必须先执行所依赖的target。<target>元素的depends属性指定所依赖的target。根据图2-10可以看出,当运行run target时,会依次执行prepare target、compile target和run target。

 

 

 

 

 

 

 

图2-10 build.xml文件中三个target的依赖关系

把helloapp应用作为独立应用程序运行的步骤如下。

(1)启动MySQL服务器。

(2)在MySQL服务器中安装helloapp应用的数据库。创建数据库的脚本为schema/sampledb.sql。在MySQL中运行该脚本,它负责创建SAMPLEDB数据库,然后在该数据库中创建CUSTOMERS表。

(3)在DOS命令行下进入helloapp根目录,然后输入如下命令:

ant run

以上命令将依次执行prepare target、compile target和run target,run target运行BusinessService类的main()方法,在DOS控制台输出很多信息,以下是部分内容:

[java] Hibernate: insert into CUSTOMERS (NAME, EMAIL, PASSWORD, PHONE, ADDRESS,

SEX, IS_MARRIED, DESCRIPTION, IMAGE, BIRTHDAY, REGISTERED_TIME, ID) values

(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

[java] Hibernate: select customer0_.ID as ID, customer0_.NAME as NAME, cust

omer0_.EMAIL as EMAIL, customer0_.PASSWORD as PASSWORD, customer0_.PHONE as PHON

E, customer0_.ADDRESS as ADDRESS, customer0_.SEX as SEX, customer0_.IS_MARRIED a

s IS_MARRIED, customer0_.DESCRIPTION as DESCRIPT9_, customer0_.IMAGE as IMAGE, c

ustomer0_.BIRTHDAY as BIRTHDAY, customer0_.REGISTERED_TIME as REGISTE12_ from CU

STOMERS customer0_ order by customer0_.NAME asc

[java] ------以下是Tom的个人信息------

[java] ID: 1

[java] 口令: 1234

[java] E-Mail: tom@yahoo.com

[java] 电话: 55556666

[java] 地址: Shanghai

[java] 性别: 男

[java] 婚姻状况: 未婚

[java] 生日: 1980-05-06

[java] 注册时间: 2005-03-15 15:10:52.0

[java] 自我介绍: I am very honest.

[java] Hibernate: select customer0_.ID as ID0_, customer0_.NAME as NAME0_,

customer0_.EMAIL as EMAIL0_, customer0_.PASSWORD as PASSWORD0_, customer0_.PHONE

as PHONE0_, customer0_.ADDRESS as ADDRESS0_, customer0_.SEX as SEX0_, customer0

_.IS_MARRIED as IS_MARRIED0_, customer0_.DESCRIPTION as DESCRIPT9_0_, customer0_

.IMAGE as IMAGE0_, customer0_.BIRTHDAY as BIRTHDAY0_, customer0_.REGISTERED_TIME

as REGISTE12_0_ from CUSTOMERS customer0_ where customer0_.ID=?

[java] Hibernate: update CUSTOMERS set NAME=?, EMAIL=?, PASSWORD=?, PHONE=?

, ADDRESS=?, SEX=?, IS_MARRIED=?, DESCRIPTION=?, IMAGE=?, BIRTHDAY=?, REGISTERED_TIME=? where ID=?

[java] Hibernate: select customer0_.ID as ID, customer0_.NAME as NAME, cust

omer0_.EMAIL as EMAIL, customer0_.PASSWORD as PASSWORD, customer0_.PHONE as PHON

E, customer0_.ADDRESS as ADDRESS, customer0_.SEX as SEX, customer0_.IS_MARRIED a

s IS_MARRIED, customer0_.DESCRIPTION as DESCRIPT9_, customer0_.IMAGE as IMAGE, c

ustomer0_.BIRTHDAY as BIRTHDAY, customer0_.REGISTERED_TIME as REGISTE12_ from CU

STOMERS customer0_ order by customer0_.NAME asc

[java] ------以下是Tom的个人信息------

[java] ID: 1

[java] 口令: 1234

[java] E-Mail: tom@yahoo.com

[java] 电话: 55556666

[java] 地址: Beijing

[java] 性别: 男

[java] 婚姻状况: 未婚

[java] 生日: 1980-05-06

[java] 注册时间: 2005-03-15 15:10:52.0

[java] 自我介绍: I am very honest.

[java] Hibernate: select customer0_.ID as ID, customer0_.NAME as NAME, cust

omer0_.EMAIL as EMAIL, customer0_.PASSWORD as PASSWORD, customer0_.PHONE as PHON

E, customer0_.ADDRESS as ADDRESS, customer0_.SEX as SEX, customer0_.IS_MARRIED a

s IS_MARRIED, customer0_.DESCRIPTION as DESCRIPT9_, customer0_.IMAGE as IMAGE, c

ustomer0_.BIRTHDAY as BIRTHDAY, customer0_.REGISTERED_TIME as REGISTE12_ from CU

STOMERS customer0_

[java] Hibernate: delete from CUSTOMERS where ID=?

由于hibernate.properties文件的show_sql属性为true,因此Hibernate把运行时执行的SQL语句输出到了控制台。当运行session.find("from Customer as c order by c. name asc")方法时,Hibernate执行的SQL语句为:

select customer0_.ID as ID, customer0_.NAME as NAME, customer0_.EMAIL as EMAIL,

customer0_.PASSWORD as PASSWORD, customer0_.PHONE as PHONE,

customer0_.ADDRESS as ADDRESS, customer0_.SEX as SEX, customer0_.IS_MARRIED

as IS_MARRIED, customer0_.DESCRIPTION as DESCRIPT9_, customer0_.IMAGE as IMAGE,

customer0_.BIRTHDAY as BIRTHDAY, customer0_.REGISTERED_TIME as REGISTE12_ from

CUSTOMERS customer0_ order by customer0_.NAME asc

当运行session.save(customer)方法时,Hibernate执行的SQL语句为:

insert into CUSTOMERS (NAME, EMAIL, PASSWORD, PHONE, ADDRESS,

SEX, IS_MARRIED, DESCRIPTION, IMAGE, BIRTHDAY, REGISTERED_TIME, ID)

values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

确切地说,以上SQL语句是在Hibernate初始化的时候创建的。在初始化时,Hibernate会根据对象-关系映射文件中的映射信息,预定义一些带参数的SQL语句,以上SQL语句中的“?”表示参数。这些预定义SQL语句存放在SessionFactory的缓存中。在运行时,Hibernate会把具体的参数值插入到SQL语句中。

图2-11显示了运行完run target之后的主要目录结构,其中,classes子目录及它包含的内容是新建的,此外,在helloapp根目录下还新建了photo_copy.gif文件。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

图2-11 运行完run target之后的helloapp应用的主要目录结构

除了用ANT来运行BusinessService类,也可以在DOS命令行下直接运行BusinessService类。假如当前路径为/helloapp,先设置classpath,把WEB-INF/classes目录以及WEB-INF/lib目录下的所有JAR文件都添加到classpath中,命令如下:

set classpath=WEB-INF/classes; WEB-INF/lib/hibernate2.jar; WEB-INF/lib/mysqldriver.jar;……

然后再执行如下命令:

java mypack.BusinessService
2.6.4 把helloapp应用作为Java Web应用运行

在本应用中创建了一个Servlet类,名为DBServlet,它在doPost()方法中调用BusinessService类的test()方法,参见例程2-6;在本应用中还创建了一个JSP文件,名为hello.jsp,它负责把请求转发给DBServlet,参见例程2-7。

例程2-6 DBServlet.jsp

package helloapp;

 

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

import java.util.*;

 

public class DBServlet extends HttpServlet {

 

public void init(ServletConfig config)

throws ServletException {

super.init(config);

}

 

public void doGet(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

 

// If it is a get request forward to doPost()

doPost(request, response);

}

 

public void doPost(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

try{

response.setContentType("text/html;charset=GB2312");

new BusinessService().test(this.getServletContext(),response.getOutputStream());

}catch(Exception e){

e.printStackTrace();

}

}

public void destroy() {

}

}

例程2-7 hello.jsp

<html locale="true">

<head>

<title>hello.jsp</title>

</head>

<body bgcolor="white">

<jsp:forward page="DBServlet" />

</body>

</html>

把helloapp应用作为Java Web应用运行的步骤如下。

(1)启动MySQL服务器

(2)在MySQL服务器中安装helloapp应用的数据库。创建数据库的脚本为schema/ sampledb.sql。在MySQL中运行该脚本,它负责创建SAMPLEDB数据库,然后在该数据库中创建CUSTOMERS表。

(3)把整个helloapp目录拷贝到<CATALINA_HOME>/webapps目录下,然后启动Tomcat服务器。

(4)在IE浏览器中访问http://localhost:8080/helloapp/DBServlet或http://localhost:8080/ helloapp/hello.jsp,都会看到如图2-12所示的输出结果。

图2-12 DBServlet的输出网页
2.7 小 结

本章通过简单的helloapp应用例子,演示如何利用Hibernate来持久化Java对象。通过这个例子,读者应该掌握以下内容。

(1)创建Hibernate的配置文件,在配置文件中提供连接特定数据库的信息。

(2)创建Hibernate的对象-关系映射文件,Hibernate根据该映射文件来生成SQL语句。本例的Customer类中包含各种Java类型的属性,如:

long、char、boolean、java.lang.String、java.sql.Date、java.sql.Timestamp和byte[]

在CUSTOMERS表中包含各种SQL类型的字段,如:

BIGINT、CHAR、BIT、VARCHAR、TEXT、DATE、TIMESTAMP和BLOB

Hibernate提供了一组内置的映射类型作为连接Java类型和SQL类型的桥梁,常用的映射类型包括:

long、character、boolean、string、text、date、timestamp和binary

(3)在应用程序中通过Hibernate API来访问数据库。在应用启动时先初始化Hibernate,创建一个SessionFactory实例;接下来每次执行数据库事务时,先从SessionFactory中获得一个Session实例,再通过Session实例来保存、更新、删除、加载或查询Java对象。

(4)掌握存储二进制大数据及长文本数据的技巧。Customer类的image属性为byte[]类型,代表二进制大数据,用于存放GIF图片的二进制数据;description属性为java. lang.String类型,代表长文本数据,用于存放长度超过255的字符串。在CUSTOMERS表中,IMAGE字段为BLOB类型,DESCRPTION字段为text类型。Hibernate的binary映射类型用于映射字节数组,text映射类型用于映射长字符串。

BusinesssService类通过Hibernate API很方便地把GIF图片及长文本数据存储到数据库中,然后又把它们检索出来。

(5)创建持久化类。持久化类不过是普通的JavaBean,Hibernate不强迫持久化类遵守特定的规范,并且持久化类无需引入任何Hibernate API,由此可以看出Hibernate没有渗透到域模型中。如果ORM软件渗透到域模型中,就意味着在持久化类中必须引入ORM软件的API,或者类的设计必须符合特定规范,这削弱了域模型的独立性和灵活性,如果日后要改用其他ORM软件,必须对模型做较大的改动。

当然,任何ORM软件都很难做到对持久化类完全没有限制,Hibernate也不例外,Hibernate要求持久化类必须提供不带参数的默认构造方法,此外,对于持久化类的集合类型的属性,Hibernate要求把属性定义为Java集合接口类型,本书第16章的16.7节(小结)对此做了详细解释。

(6)在开发Java应用时,为了提高开发效率,缩短开发周期,常常需要集成第三方提供的Java软件,除了本书重点介绍的ORM映射工具Hibernate,还有其他如MVC框架Struts、日志工具Log4J、Web服务软件Apache AXIS等。在自己的应用中集成这些第三方软件时,大体步骤都很相似。

(1)把它们的JAR文件拷贝到classpath中。

(2)创建它们的配置文件(XML格式的文件或者Java属性文件),这些配置文件通常也位于classpath中。

(3)在程序中访问它们的API。

作为软件使用者,如果仅仅想快速掌握一个新的Java软件的使用方法,而不打算深入了解软件内在原理和结构,无非就是了解它的API及配置文件的使用方法。当然,如果想对软件的运用达到得心应手的地步,还应该了解软件本身的组成原理和结构。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/gnemy/archive/2006/12/07/1433701.aspx

原创粉丝点击