1、第三十四章:Hibernate 基础 ITJob 就业培训677第三十四章:Hibernate 基础学习目标 理解 ORM 机制 理解 Hibernate 的工作原理 Hibernate 的配置和对象-映射文件 理解对象持久化第三十四章:Hibernate 基础 ITJob 就业培训678Hibernate 简介Hibernate 是 Java 应用和关系数据库之间的桥梁,它负责 Java 对象关系数据之间的映射。Hibernate 内部封装了通过 JDBC 访问数据库的操作,向上层应用提供了面向对象的数据访问 API。在 Java 应用中使用 Hibernate 包含以下步骤。(1) 创建
2、Hibernate 的配置文件。 (2) 创建持久化类。 (3) 创建对象-关系映射文件。 (4) 通过 Hibernate API 编写访问数据库的代码。建立简单的 Hibernate 应用 本章通过一个简单的例子 customerApp 应用,演示如何运用 Hibernate 来访问关系数据库。customerApp 应用的功能非常简单:通过 Hibernate 保存、更新、删除、加载以及查询 Customer 对象。创建 Hibernate 的配置文件Hibernate 从其配置文件中读取和数据库连接有关的信息,这个配置文件应该位于应用的 classpath 中。Hibernate 的配
3、置文件有两种形式:一种是 XML 格式的文件;还有一种是 Java 属性文件,采用“健=值”的形式。 下面介绍如何以Java 属性文件的格式来创建 Hibernate 的配置文件。这种配置文件的默认文件名为 hibernate.properties。hibernate.properties 的内容如下:hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect hibernate.connection.driver_class=com.mysql.jdbc.Driverhibernate.connection.url=jdbc:mysql:/
4、localhost:3306/SAMPLEB hibernate.connection.username=root hibernate.connection.password=1234 hibernate.show_sql=true以上 hibernate.properties 文件包含了一系列属性及其属性值,Hibernate 将根据这些属性来连接数据库,本例为连接 MySQL 数据库的配置代码。下表对以上 hibernate.properties 文件中的所有属性做了描述。属性 描述hibernate.dialect SQL 指定数据库使用的方言hibernate.connection.d
5、river_class 指定数据库的驱动程序hibernate.connection.url URL 指定连接数据库的hibernate.connection.username 指定连接数据库的用户名hibernate.connection.password 指定连接数据库的口令hibernate.show_sql 如果为 true,表示在程序运行时,会在控制台输出SQL 语句,这有利于跟踪 Hibernate 的运行状态。默认为 false。在应用开发和测试阶段,可以把这个属性设为 true,以便跟踪和调试应用程序,在应用发布阶段,应该把这个属性设为 false,以便减少应用的输出信息,提高
6、运行性能。Hibernate 能够访问多种关系数据库,如 MySQL、Oracle 和 Sybase 等。尽管多数关系数据库都支持标准的 SQL 语言,但是它们往往还有各自的 SQL 方言,就象不同地区的人既能说标准的普通话,还能讲各自的方言一样。第三十四章:Hibernate 基础 ITJob 就业培训679hibernate.dialect 属性用于指定被访问数据库使用的 SQL 方言,当 Hibernate 生成 SQL 查询语句,或者使用 native 对象标识符生成策略时,都会参考本地数据库的 SQL 方言。创建持久化类持久化类是指其实例需要被 Hibernate 持久化到数据库中的
7、类。持久化类通常都是域模型中的实体域类。持久化类符合 JavaBean 的规范,包含一些属性,以及与之对应的 getXXX()和 setXXX()方法。以下定义了一个名为 Customer 的持久化类。package com.itjob.jiaowu.hibernate; import java.io.Serializable; import java.sql.Date; import java.sql.Timestamp; public class Customer implements Serializable private Long id; private String name; p
8、rivate 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 set
9、Id(Long id) this.id = id; public String getName() return name; public void setName(String name) this.name=name; 第三十四章:Hibernate 基础 ITJob 就业培训680/此处省略 email、password 和 phone 等属性的 getXXX()和 setXXX()方法 持久化类符合 JavaBean 的规范,包含一些属性,以及与之对应的 getXXX()和 setXXX()方法。getXXX()和 setXXX()方法必须符合特定的命名规则, “get”和“set”后
10、面紧跟属性的名字,并且属性名的首字母为大写,例如 name 属性的 get 方法为 getName(),如果把 get方法写为 getname()或者 getNAME(),会导致 Hibernate 在运行时抛出以下异常:net.sf.hibernate.PropertyNotFoundException: Could not find a getter for property name in class com.itjob.jiaowu.hibernate.Customer 如果持久化类的属性为 boolean 类型,那么它的 get 方法名既可以用“get”作为前缀,也可以用“is”作为
11、前缀。例如 Customer 类的 married 属性为 boolean 类型,因此以下两种 get 方法是等价的: public boolean isMarried() return married; 或者 public boolean getMarried() return married; Hibernate 并不要求持久化类必须实现 java.io.Serializable 接口,但是对于采用分布式结构的 Java 应用,当 Java 对象在不同的进程节点之间传输时,这个对象所属的类必须实现 Serializable 接口,此外,在 Java Web 应用中,如果希望对 HttpSe
12、ssion 中存放的 Java 对象进行持久化,那么这个 Java 对象所属的类也必须实现 Serializable 接口。 Customer 持久化类有一个 id 属性,用来惟一标识 Customer 类的每个对象。在面向对象术语中,这个 id 属性被称为对象标识符(OID,Object Identifier) ,通常它都用整数表示,当然也可以设为其他类型。如果 customerA.getId().equals(customerB.getId()的结果是 true,就表示 customerA 和 customerB 对象指的是同一个客户,它们和 CUSTOMERS 表中的同一条记录对应。 H
13、ibernate 要求持久化类必须提供一个不带参数的默认构造方法,在程序运行时,Hibernate 运用 Java 反射机制,调用java.lang.reflect.Constructor.newInstance()方法来构造持久化类的实例。如果对这个持久化类使用延迟检索策略,为了使 Hibernate 能够在运行时为这个持久化类创建动态代理,要求持久化类的默认构造方法的访问级别必须是 public 或 protected 类型,而不能是 default 或 private 类型。在 Customer类中没有引入任何 Hibernate API,Customer 类不需要继承 Hibernat
14、e 的类,或实现 Hibernate 的接口,这提高了持久化类的独立性。如果日后要改用其他的ORM 产品,比如由 Hibernate 改为 OJB,不需要修改持久化类的代码。创建数据库 Schema在本例中,与 Customer 类对应的数据库表名为 CUSTOMERS,它在 MySQL 数据库中的 DDL 定义如下: create table CUSTOMERS( ID bigint not null primary key, 第三十四章:Hibernate 基础 ITJob 就业培训681NAME varchar(15) not null, EMAIL varchar(128) not n
15、ull, 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 类型,参见下表。字段名 SQL 类型 说明ID BIGINT 整数,占 8 字节,取值范围为:-2
16、63 263-1NAME VARCHAR 变长字符串,占 0 255 个字节SEX CHAR 定长字符串,占 0 255 个字节IS_MARRIED BIT 布尔类型DESCRIPTION TEXT 长文本数据,占 0 65535 255 字节。如果字符串长度小于 255 ,可以用 VARCHAR 或 CHAR 类型来表示。如果字符串长度大于 255 ,可以定义为 TEXT 类型。IMAGE BLOB 二进制长数据,占 0 65535 字节,BLOB 是 Binary Large Object 的缩写。IMAGE 在本例中, 字段用来存放图片数据BIRTHDAY DATE 代表日期,格式为“Y
17、YYY-MM-DD”REGISTERED_TIME TIMESTAMP 代表日期和时间,格式为“YYYYMMDDHHMMSS ”创建对象-关系映射文件Hibernate 采用 XML 格式的文件来指定对象和关系数据之间的映射。在运行时,Hibernate 将根据这个映射文件来生成各种 SQL 语句。在本例中,将创建一个名为 Customer.hbm.xml 的文件,它用于把 Customer 类映射到 CUSTOMERS 表,这个文件应该和 Customer.class 文件存放在同一个目录下。以下为Customer.hbm.xml 文件的源代码。第三十四章:Hibernate 基础 ITJo
18、b 就业培训682映射文件的文档类型定义(DTD)Customer.hbm.xml 文件的开头声明了 DTD(Document Type Definition, 文档类型定义) ,它对 XML 文件的语法和格式作了定义。Hibernate 的 XML 解析器将根据 DTD 来核对 XML 文件的语法。 每一种 XML 文件都有独自的 DTD 文件。Hibernate 的对象-关系映射文件使用的 DTD 文件的下载网址为:http:/ Hibernate 软件包的 srcnetsfhibernate 目录下也提供了 hibernate-mapping-2.0.dtd 文件。在这个文件中,描述顶层
19、元素的代码如下: 描述顶层元素的子元素的代码如下:第三十四章:Hibernate 基础 ITJob 就业培训683元素是对象-关系映射文件的根元素,其他元素(即以上DTD 代码中括号以内的元素,如子元素)必须嵌入在元素以内。在元素中又嵌套了好多子元素。在以上 DTD 代码中,还使用了一系列的特殊符号来修饰元素,下表描述了这些符号的作用。在创建自己的对象-关系映射文件时,如果不熟悉某种元素的语法,可以参考 DTD 文件。DTD 中特殊符号的作用符号 含义无符号 该子元素在父元素内必须存在且只能存在一次。+ 该子元素在父元素内必须存在,可以存在一次或者多次。* 该子元素在父元素内可以不存在,或者存
20、在一次或者多次。它是比较常用的符号。? 该子元素在父元素内可以不存在,或者只存在一次。它是比较常用的符号。表 2-3 可以看出,在元素中,、和等子元素可以不存在,或者存在一次或者多次;在元素中,子元素必须存在且只能存在一次,元素可以不存在,或者存在一次或者多次。 此外,在映射文件中,父元素中的各种子元素的定义必须符合特定的顺序。例如根据元素的 DTD 可以看出,必须先定义子元素,再定义子元素,以下映射代码颠倒了和子元素的位置: Hibernate 的 XML 解析器在运行时会抛出 MappingException:java 21:27:51,610 ERROR XMLHelper:48 - E
21、rror 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-sub
22、class*)“.java net.sf.hibernate.MappingException: Error reading resource: mypack/Customer.hbm.xml at net.sf.hibernate.cfg.Configuration.addClass(Configuration.java:357)把 Customer 持久化类映射到 CUSTOMERS 表Customer.hbm.xml 文件用于映射 Customer 类。如果需要映射多个持久化类,那么既可以在同一个映射文件中映射所有类,也可以为每个类创建单独的映射文件,映射文件和类同名,扩展名为“hbm.
23、xml” 。后一种做法更值得推荐,因为在团队第三十四章:Hibernate 基础 ITJob 就业培训684开发中,这有利于管理和维护映射文件。 元素指定类和表的映射,它的name 属性设定类名,table 属性设定表名。以下代码表明和 Customer 类对应的表为 CUSTOMERS 表: 如果没有设置元素的 table 属性,Hibernate 将直接以类名作为表名,也就是说,默认情况下,与 mypack.Customer 类对应的表为 Customer 表。 元素包含一个子元素以及多个子元素。子元素设定持久化类的 OID 和表的主键的映射。以下代码表明 Customer 类的 id 属
24、性和 CUSTOMERS 表中的 ID 字段对应。元素的子元素指定对象标识符生成器,它负责为 OID 生成惟一标识符。子元素设定类的属性和表的字段的映射。子元素主要包括 name、type、column 和 not-null 属性。 1元素的 name 属性 元素的 name 属性指定持久化类的属性的名字。 2元素的 type 属性 元素的 type 属性指定 Hibernate 映射类型。Hibernate 映射类型是Java 类型与 SQL 类型的桥梁。3元素的 not-null 属性 如果元素的 not-null 属性为 true,表明不允许为 null,默认为 false。例如以下代码
25、表明不允许 Customer 类的 name 属性为 null: Hibernate 在持久化一个 Customer 对象时,会先检查它的 name 属性是否为 null,如果为 null,就会抛出以下异常: net.sf.hibernate.PropertyValueException: not-null property references a null or transient value: com.itjob.jiaowu.hibernate.Customer.name 如果数据库中 CUSTOMERS 表的 NAME 字段不允许为 null,但在映射文件中没有设置 not-null
26、 属性: 那么 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“4元素的 column 属性 元素的 column 属性指定与类的属性映射的表的字段名。以下代码表
27、明和 address 属性对应的字段为 ADDRESS 字段: 如果没有设置元素的 column 属性,Hibernate 将直接以类的属性名作为字段名,也就是说,默认情况下,与 Customer 类的 address 属性对应的字段为 address 字段。 元素还可以包括子元素,它和第三十四章:Hibernate 基础 ITJob 就业培训685元素的 column 属性一样,都可以设定与类的属性映射的表的字段名。以下两种设置方式是等价的: 或者 元素的子元素比 column 属性提供更多的功能,它可以更加详细的描述表的字段。例如以下子元素指定 CUSTOMERS 表中的 NAME 字段的
28、 SQL 类型为 varchar(15),不允许为 null,并且为这个字段建立了索引:通过 Hibernate API 操纵数据库Hibernate 对 JDBC 进行了封装,提供了更加面向对象的 API。以下两图对比了直接通过 JDBC API 以及通过 Hibernate API 来访问数据库的两种方式。 以下示例的 BusinessService 类演示了通过 Hibernate API 对 Customer 对象进行持久化的操作。package com.itjob.jiaowu.hibernate; import javax.servlet.*; import net.sf.hibe
29、rnate.*; 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 基础 ITJob 就业培训686/ 根
30、据默认位置的 Hibernate 配置文件的配置信息,创建一个Configuration 实例 Configuration config = new Configuration(); config.addClass(Customer.class); / 创建 SessionFactory 实例 */ sessionFactory = config.buildSessionFactory(); catch(Exception e)e.printStackTrace(); /* 查询所有的 Customer 对象,然后调用 printCustomer()方法打印 Customer对象信息 */ p
31、ublic 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 /*删除所
32、有的 Customer 对象 */ public void deleteAllCustomers() throws Exception Session session = sessionFactory.openSession(); Transaction tx = null; try tx = session.beginTransaction(); session.delete(“from Customer as c“); mit(); catch (Exception e) if (tx != null) tx.rollback(); throw e; finally session.clo
33、se(); /* 选择向控制台还是动态网页输出 Customer 对象的信息 */ private void printCustomer(ServletContext context,OutputStream out,Customer customer) throws Exception if(out instanceof ServletOutputStream) 第三十四章:Hibernate 基础 ITJob 就业培训687printCustomer(context,(ServletOutputStream) out,customer); else printCustomer(PrintS
34、tream) 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(Servl
35、etContext context,OutputStream out) throws Exception Customer customer=new Customer(); customer.setName(“Tom“); “); customer.setEmail(“customer.setPassword(“1234“); customer.setPhone(55556666); customer.setAddress(“Shanghai“); customer.setSex(M); customer.setDescription(“I am very honest.“); /设置 Cus
36、tomer 对象的 image 属性,它是字节数组,存放 photo.gif 文件中的二进/photo.gif 文件和 BusinessService.class 文件位于同一个目录下 InputStream in=this.getClass().getResourceAsStream(“photo.gif“); byte buffer = new bytein.available(); in.read(buffer); customer.setImage(buffer); /设置 Customer 对象的 birthday 属性,它是 java.sql.Date 类型 customer.se
37、tBirthday(Date.valueOf(“1980-05-06“); saveCustomer(customer); findAllCustomers(context,out); loadAndUpdateCustomer(customer.getId(),“Beijing“); findAllCustomers(context,out); deleteAllCustomers(); 第三十四章:Hibernate 基础 ITJob 就业培训688public static void main(String args) throws Exception new BusinessServi
38、ce().test(null,System.out); sessionFactory.close(); 以上例子演示了通过 Hibernate API 访问数据库的一般流程。首先应该在应用的启动阶段对 Hibernate 进行初始化,然后就可以通过 Hibernate 的 Session 接口来访问数据库。Hibernate 的初始化 BusinessService 类的静态代码块负责 Hibernate 的初始化工作,如读取 Hibernate 的配置信息以及对象-关系映射信息,最后创建 SessionFactory 实例。当 JVM(Java 虚拟机)加载 BusinessService
39、类时,会执行该静态代码块。初始化过程包括如下步骤。(1)创建一个 Configuration 类的实例,Configuration 类的构造方法把默认文件路径下的 hibernate.properties 配置文件中的配置信息读入到内存: Configuration config = new Configuration(); (2)调用 Configuration 类的 addClass(Customer.class)方法: config.addClass(Customer.class); 该方法把默认文件路径下的 Customer.hbm.xml 文件中的映射信息读入到内存中。(3)调用 C
40、onfiguration 类的 buildSessionFactory()方法: sessionFactory = config.buildSessionFactory(); 该方法创建一个 SessionFactory 实例,并把 Configuration 对象包含的所有配置信息拷贝到 SessionFactory 对象的缓存中。SessionFactory 代表一个数据库存储源,如果应用只有一个数据库存储源,那么只需创建一个 SessionFactory 实例。当 SessionFactory 对象创建后,该对象不和 Configuration 对象关联。因此如果再修改 Configur
41、ation 对象包含的配置信息,不会对 SessionFactory对象有任何影响。访问 Hibernate 的 Session 接口初始化过程结束后,就可以调用 SessionFactory 实例的 openSession()方法来获得 Session 实例,然后通过它执行访问数据库的操作。Session 接口提供了操纵数据库的各种方法,如: save()方法:把 Java 对象保存数据库中。 update()方法:更新数据库中的 Java 对象。 delete()方法:把 Java 对象从数据库中删除。 load()方法:从数据库中加载 Java 对象。 find()方法:从数据库中查询
42、Java 对象。 Session 是一个轻量级对象。通常将每一个 Session 实例和一个数据库事务绑定,也就是说,每执行一个数据库事务,都应该先创建一个新的 Session 实例。如果事务执行中出现异常,应该撤销事务。不论事务执行成功与否,最后都应该调用 Session 的 close()方法,从而释放 Session 实例占用的资源。以下代码演示了用 Session 来执行事务的流程,其中 Transaction 类用来控制事务。 Session session = factory.openSession(); 第三十四章:Hibernate 基础 ITJob 就业培训689Transa
43、ction tx;try /开始一个事务 tx = session.beginTransaction(); /执行事务 . /提交事务 mit(); catch (Exception e) /如果出现异常,就撤销事务 if (tx!=null) tx.rollback(); throw e; finally /不管事务执行成功与否,最后都关闭 Session session.close(); 下图为正常执行数据库事务(即没有发生异常)的时序图。BusinessService 类提供了保存、删除、查询和更新 Customer 对象的各种方法。BusinessService 类的 main()方法
44、调用 test()方法,test()方法又调用以下方法:1saveCustomer()方法 该方法调用 Session 的 save()方法,把 Customer 对象持久化到数据库中。 tx = session.beginTransaction(); session.save(customer); mit(); 第三十四章:Hibernate 基础 ITJob 就业培训690当运行 session.save()方法时,Hibernate 执行以下 SQL 语句: insert into CUSTOMERS (ID, NAME, EMAIL, PASSWORD, PHONE, ADDRESS,
45、 SEX, IS_MARRIED,DESCRIPTION, IMAGE, BIRTHDAY, REGISTERED_TIME) ,1234,55556666,Shanghai,M,0,I am very honest., values(1,Tom,?,1980-05-06,null) 在 test()方法中并没有设置 Customer 对象的 id 属性,Hibernate 会根据映射文件的配置,采用 increment 标识符生成器自动以递增的方式为 OID 赋值。在 Customer.hbm.xml 文件中相关的映射代码如下: 在 test()方法中也没有设置 Customer 对象的 r
46、egisteredTime 属性,因此在以上 insert 语句中,REGISTERED_TIME 字段的值为 null。但由于 REGISTERED_TIME 字段的 SQL 类型为 TIMESTAMP 类型,如果 insert 语句没有为 TIMESTAMP 类型的字段赋值,底层数据库会自动把当前的系统时间赋值给TIMESTAMP 类型的字段。因此,执行完以上 insert 语句后,REGISTERED_TIME 字段的值并不为 null,而是插入该记录时的系统时间。 2findAllCustomers()方法 该方法调用 Session 的 find()方法,查询所有的 Customer
47、 对象。 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(); mit(); 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.b