第1章 对象关系映射基础
本章首先从对象持久化讲起,引导读者明确什么是对象关系映射,对象关系映射有什么重要的作用。明确这一点之后,读者便会明白Hibernate是用来做什么的了。
第2章 Hibernate简介
本章将使读者了解到Hibernate的发展历史、产品情况、主要接口以及技术发展趋势,对这项技术的背景有一个简单的了解。
第1章 对象关系映射基础
【本章知识导学】
对象是现实世界中各种实体在计算机世界中的抽象,关系数据库中则同样可以存储代表着现实实体的相关数据,其物理形式是一个个数据文件,逻辑形式则是关系,即关系数据库中的表。
Hibernate中间件是连接Java对象和关系型数据库的桥梁,将Java对象映射为各种关系型数据库中的二维关系,或者反之,将关系数据库表中所存储的关系映射为Java对象,这便是Hibernate的主要职责。同时,Hibernate还屏蔽了Java对于各种关系型数据库的操作,使得程序员对数据库的编程变得更加容易。
本章首先从对象持久化讲起,引导读者明确什么是对象关系映射,对象关系映射有什么重要的作用。明确这一点之后,读者便会明白Hibernate是用来做什么的了。
1.1 对象持久化
“持久”(Persistence)是与“暂时”、“临时”(Transient)等相对应的一个概念,举个例子来讲,在计算机内存(RAM)中存储的数据在切断电源之后便全部丢失,因此这些数据是暂时、临时的,而存储在ROM、硬盘或者光盘中的数据则不会这样轻易丢失,此所谓持久数据。所以“持久化”的概念就是将数据从“临时”状态转换为“持久”状态。持久化的途径包括很多种,最常见的方式是使用文件来存储持久化数据,正如在我们的硬盘中,绝大多数数据都是以文件的形式存在的。
软件世界发展到面向对象的阶段之后,现实世界中的实体都被以对象的形式来表示。对象是一组数据以及对这些数据操作的集合,是现实世界中实体的反映,它可以更加真实地反映这个世界,更加符合人类的思维规律。
这里请读者区分一下数据持久化和对象持久化这两个概念。数据持久化是对象持久化的基础,对象持久化的主要操作对象是对象里的数据,也就是属性。当然,对象的属性仍然可以是对象,那么这里便要对对象属性进行进一步的持久化操作。不做特殊说明,本书在后面的章节中所提到的“持久化”均指对象持久化。
对象代表实体,而实体最通常的存储方式便是关系型数据库,另外,实体之间的关系也在关系数据库中以单一主键、联合主键、外键等方式得以体现。因此将对象持久化到关系型数据库中是目前对象持久化技术大多采用的方式。
1.2 对象-关系映射
J2EE体系结构通常将基于Web的软件系统分为多层,如三层架构(浏览器层、应用层、数据库层)等,这种多层的体系架构可以使系统结构和功能逻辑清晰明了,便于系统模块的拆分组合,让程序员容易明确项目目标,获得清晰的分工和责任,使整个程序开发过程有序可控,同时清晰的架构分层和模块划分,也使各对象间相互独立,保证了系统的可维护性和可扩展性,也便于程序员在开发过程中及时发现错误原因,迅速明确错误位置,保证单元模块的开发质量,降低了导致系统集成错误的因素。
对象-关系映射(Object/Relation Mapping,简称ORM),是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
用来完成对象-关系映射的软件层一般被称为持久化层,其职责是持久化以及反持久化,也就是将内存中的对象及对象之间的关系持久化到数据库表所表示的关系中,或者将数据库中的二维关系示例化到内存中。因此,持久化层通常被置于应用层和数据库层之间。图1-1表示的是从应用程序直接访问数据库的体系结构和Hibernate的体系结构。
图1-1 Hibernate的体系结构
应用程序直接访问数据库时,程序员需要亲自编写读写数据库的代码,不仅要维护实体,还要维护关系,另外还要进行关系与对象的转换,从而使应用程序可以对数据库中所存储的实体与关系进行操作。举一个简单的例子,如通过应用程序直接访问数据库,实现学生信息、课程信息以及选课信息的管理。程序员需要编写程序操作两个实体(学生、课程)和一个关系(选课),一般情况下对应到数据库就需要对三个表(学生信息、课程信息和选课信息)进行读、写、关联等操作,稍有不慎便容易造成数据不一致的后果,导致应用程序的逻辑错误。
通过Hibernate的对象-关系映射,程序员即可免去繁重的编码工作量,只需要在映射文件中对关系进行定义,然后编写少量的代码,便可将实体与关系的维护以及对象与关系的转换工作交由Hibernate代劳。Hibernate是通过配置文件和映射文件来为应用层和数据库层提供持久化服务的。由于需要读取文件信息和产生持久化对象,因此Hibernate在提供持久化服务的同时,也势必会增大时间和空间的开销,在一定程度上降低系统的运行效率。但只要充分利用对象-关系映射并对其进行合理的优化,Hibernate在关系操作上的运行效率便可大大抵消文件与对象操作的不利影响。
1.3 对象持久化的实现模式
对象持久化有许多实现模式,除了ORM方式和下一节将要提到的通过JDBC直接访问数据库这两种方式外,还包括主动域对象模式、CMP模式、JDO模式等,这几种方式不是本书的主要讲解内容,因此仅在本节作简单介绍。
1.3.1 主动域对象模式
主动域对象是实体域对象的一种形式,它在实现中封装了关系数据模型和数据访问的细节,使应用程序代码避免了与数据库的任何直接交互。主动域对象公开了逻辑操作,而不只是简单地表示数据。
主动域对象模式有以下优点:
● 清晰的应用程序代码。在实体域对象中封装自身的数据访问细节,过程域对象完全负责业务逻辑,使程序结构更加清晰。
● 松耦和关系的程序代码与数据模式。如果关系数据模式发生变化,只需要修改主动域对象的代码,不需要修改过程域对象的业务方法。
● 把相关的数据访问代码组织到单个域对象中,便于各个域对象的数据访问。
主动域对象模式有以下缺点:
● 在实体域对象的实现中仍然包含SQL语句。
● 数据访问分布在多个域对象中。这会导致实体域对象重复实现一些共同的数据访问操作,从而造成重复编码。
● 由于数据访问代码被分布在各个域对象中,限制了应用程序对数据访问的控制。
主动域对象本身位于业务逻辑层,因此采用主动域对象模式时,整个应用仍然是三层应用结构,并没有从业务逻辑层分离出独立的持久化层。
1.3.2 CMP模式
在J2EE架构中,CMP(Container-Managed Persistence,容器管理持久性)表示由EJB容器来管理实体EJB的持久化,EJB容器封装了对象-关系的映射及数据访问细节。在这种设计模式下,业务对象不用考虑数据的细节(如来源、存放等)。
CMP模式的优点在于:
● 简化了代码并且加速应用程序的开发。
● 基于EJB技术,是Sun J2EE体系的核心部分,获得了大厂商和开源组织的普遍支持,技术支持非常完备。
● 功能日趋完善,包括了完善的事务支持、更高的性能、EJBQL查询语言,透明的分布式访问等等。
CMP模式的缺点在于:
● 缺乏灵活性。开发人员开发的实体必须遵守复杂的J2EE规范,而ORM中间件没有类似要求。实体域EJB只能运行在EJB容器中,兼容性差。
● 在此种模式下,调试工作比较复杂。
1.3.3 JDO模式
JDO(Java Data Objects,Java数据对象)是近几年新兴的数据持久性技术,JDO是Sun公司制定的描述对象持久化语义的标准API。它支持把对象持久化到任意一种存储系统中,包括关系数据库、面向对象数据库、基于XML数据库,以及其他存储系统,因此,从某种角度来看,它并不是对象关系映射接口。
JDO模式的优点是:
● 简单易用,不需要写大量无用的接口。不需要继承什么特殊的类,唯一所要做的就是增强一下程序员的class文件。
● 真正面向对象。用了JDO的Java程序是真正的面向对象,不需要了解数据库中很复杂的内幕,存取都是以Java Object为对象,所有数据库表格自动生成。
● 方便的数据库移植。应用程序换数据库的时候除了换一个JDBC driver和数据库URL,无需对程序做任何改动。
JDO模式的缺点是:
● 由于是新兴技术,业内还没有给予足够的支持,其中包括IBM、Oracle、BEA。
● 查询支持不全,并且用于重载方法的字段扩展让人觉得比较混乱和复杂。
1.4 感受ORM
通过上述讲解,读者已经明确Hibernate是做什么的了。现在本书通过两个例子:JDBC直接操作数据库和Hibernate操作数据库来进行一个对照,切身体会一下两者的区别,读者将更加认识到Hibernate给程序员带来的便利。如果读者对Hibernate的配置环境不是很了解,可以先参考第3章,对开发环境进行配置后,再运行例子。
1.4.1 JDBC操作数据库
JDBC(Java Database Connectivity,Java数据库连接),是Java访问数据库的程序接口,由一组用Java语言编写的类与接口组成。正如ODBC(Open Database Connectivity,开放数据库连接)所实现的开放数据库互联一样,JDBC实际上就是由Java实现的数据库访问中间件。开发人员可以通过JDBC向各种关系型数据库发送SQL语句,只需要使用JDBC提供的几个类(对象)或接口即可,而不必为不同的数据库编写不同的程序。而且,由于Java语言的跨平台性,开发人员也不必为不同的平台编写不同的程序,只需要编写一遍程序即可以达到“Write Once,Run Everywhere”的目的。
JDBC是一种在业务方法中直接嵌入SQL语句的直接访问数据库的方式,SQL语句是面向关系的,依赖于关系模型。所以JDBC方式的优点是简单直接,特别是在小型应用方面十分方便。但是JDBC这种实现方式给应用程序带来以下缺点:
● 实现业务逻辑的代码和数据库访问代码掺杂在一起,使程序结构不清晰,可读性差。
● 在程序代码中嵌入面向关系的SQL语句,使开发人员不能完全运用面向对象的思维来编写程序。
● 业务逻辑和关系数据模型绑定,如果关系数据发生变化,必须手工修改代码中所有相关的SQL语句,这增加了维护软件的难度。
● 如果程序代码中SQL语句有语法错误,则在编译时不能检查到这种错误,只有到运行时才能发现这种错误,这增加了调试程序的难度。
JDBC提供的类(对象)或接口主要有:
● DriverManager类:JDBC的管理层是作用于用户和驱动程序之间。它跟踪可用的驱动程序,并在数据库和相应驱动程序之间建立连接。
● Connection对象:代表与数据库的连接,连接过程包括所执行的SQL语句和通过该连接所返回的结果。
● Statement对象:用于将SQL语句发送到数据库中。
● ResultSet对象:包含符合SQL语句条件的所有行以及对这些行的访问方法。
下面通过一个非常简单的程序示例(例1-1)对JDBC操作数据的过程进行讲解。
例1-1
本程序实现对一个图书借阅系统的图书信息进行增加、删除、修改以及查询的操作。图书信息包括图书编号、书名、作者姓名、出版社名称、出版日期、单价、数量等信息,存储在数据库HibernateWizard中的C1_Book表中。本书采用Microsoft SQL Server 2000开发版作为本书所有示例的关系数据库系统,其他关系型数据库的操作本书不做讲解,有兴趣的读者可以查阅其他书籍或资料进行学习。
数据库表C1_Book的数据结构如表1-1所示。
表1-1 数据库表C1_Book的数据结构
其数据库DLL语句如下所示:
CREATE TABLE [dbo].[C1 Book] ( [BookId] [varchar] (5) COLLATE Chinese PRC CI AS NOT NULL , [BookName] [varchar] (50) COLLATE Chinese PRC CI AS NOT NULL , [Author] [varchar] (20) COLLATE Chinese PRC CI AS NULL , [PublishOrg] [varchar] (50) COLLATE Chinese PRC CI AS NULL , [PublishDate] [datetime] NULL , [Price] [float] NULL , [Amount] [int] NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[C1 Book] ADD CONSTRAINT [PK C1 Book] PRIMARY KEY CLUSTERED ( [BookId] ) ON [PRIMARY] GO
【例1-1】JDBC操作数据库
文件JdbcOperation.java
package hibernate.wizard.chapter1; //加载Java数据连接包,Java基本所有的数据库的调用都在这个包里面 import java.sql.*; public class JdbcOperation { public static void main(String args[]){ JdbcOperation jo=new JdbcOperation(); jo.execute(); } private void execute() { //取得连接的url名 String url= "jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=HibernateWizard"; //声明一个Connection对象 Connection con; Statement stmt; //SQL数据库查询语句 String query= "select * from C1 Book"; try { //加载SQL Server的jdbc驱动 Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); } catch (java.lang.ClassNotFoundException e) { System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); } try { // 建立数据库连接 con= DriverManager.getConnection(url, "sa", "sa"); //将数据库连接设置为自动提交模式 con.setAutoCommit(true); stmt= con.createStatement(); //执行insert into语句 Stmt.executeUpdate ("insert into C1 Book (BookId,BookName) values('00001','Hibernate名师导学')"); stmt .executeUpdate ("insert into C1 Book (BookId,BookName) values('00002','struts名师导学')"); queryBooks(con,stmt,query); //执行一个update语句,更新数据库 stmt .executeUpdate ("update C1 Book set BookName='html名师导学' where BookId='00001'"); queryBooks(con,stmt,query); stmt.executeUpdate("delete from C1 Book where BookId='00002'"); queryBooks(con,stmt,query); stmt.close(); con.close(); // 上面的语句关闭声明和连接 } catch (SQLException ex) { System.err.println("SQLException: " + ex.getMessage()); } } private void queryBooks(Connection con,Statement stmt,String query){ try{ //返回一个结果集 ResultSet rs= stmt.executeQuery(query); //下面的语句使用了一个while循环打印出了C1 Book表中的所有数据 System.out.println("C1 Book表中的数据如下"); System.out.println("图书编号" + " " + "书名"); System.out.println("------" + " " + "------"); while (rs.next()) { //取得数据库中的数据 String s= rs.getString("BookId"); String f= rs.getString("BookName"); System.out.println(s + " " + f); } rs.close(); }catch(SQLException ex) { System.err.println("SQLException: " + ex.getMessage()); } } }
程序运行结果如图1-2所示。
图1-2 例1-1运行结果
【提示】
JDBC连接数据库有多种方式,本示例中使用的是通过加载JDBC驱动程序来进行数据库连接的方式。对于JDBC依次打开的Connection、Statement、ResultSet等对象,在数据库操作完成之后要按照相反的顺序依次关闭,否则会随着程序的多次执行逐渐耗尽资源,造成系统崩溃等后果。为了介绍方便,本示例将数据库事务设置为自动提交模式,但在一般情况下,需要对数据库操作尤其是数据库更新操作进行显式的事务处理,不建议使用自动提交模式。
通过例1-1读者可以看出,JDBC方法操作比较简单,只需要建立数据库连接,将SQL语句作为参数传递给Statement对象即可,这是一种直接操作数据库的方式。
1.4.2 Hibernate操作数据库
Hibernate的运行需要持久化类文件、配置文件和映射文件的支持,本程序示例不对这些文件进行讲解,仅对关键的数据库操作进行讲解。读者可直接将本书光盘中的程序示例源代码编译运行,目的是对Hibernate的数据库操作有一个初步的认识。
读者可以通过程序示例(例1-2)来了解Hibernate对数据库的操作。
例1-2业务说明同例1-1。
【例1-2】Hibernate操作数据库
文件EventManager.java
package hibernate.wizard.chapter1; import org.hibernate.Transaction; import org.hibernate.Session; import org.hibernate.Query; import java.util.ArrayList; public class EventManager { public static void main(String args[]) { EventManager mgr= new EventManager(); mgr.execute(); //关闭SessionFactory,HibernateOperation类是一个用于获取Session的工具类 HibernateOperation.sessionFactory.close(); } private void execute() { //HQL数据库查询语句 String query= "select book.bookId,book.bookName from hibernate.wizard.chapter1.C1Book as book"; //获得Session的示例 Session session= HibernateOperation.currentSession(); //开始一个事务 Transaction tx= session.beginTransaction(); //生成一个持久化类的示例,C1Book类就是持久化类,代表数据库中的C1 Book表 //一个C1Book类的处于持久化状态的示例就对应C1 Book表中的一行 //操作持久化类的示例就相当于操作数据库表 C1Book book1= new C1Book(); book1.setBookId("00003"); book1.setBookName("专家导学CSS"); C1Book book2= new C1Book(); book2.setBookId("00004"); book2.setBookName("专家导学JavaScript"); session.save(book1); //保存图书信息,即持久化的过程 session.save(book2); //保存图书信息,即持久化的过程 queryBooks(session,query); //查询所有图书信息 session.flush(); session.load(C1Book.class, "00003"); book1.setBookName("专家导学EJB"); //修改图书信息 session.update(book1); //修改图书信息 queryBooks(session,query); session.flush(); session.delete(book2); //删除图书信息 queryBooks(session,query); session.flush(); tx.commit(); HibernateOperation.closeSession(); //关闭session } private void queryBooks(Session session,String query){ Query result=(Query)session.createQuery(query); //执行HQL数据库查询语句 ArrayList a=(ArrayList)result.list(); System.out.println("C1 Book表中的数据如下"); System.out.println("图书编号" + " " + "书名"); System.out.println("------" + " " + "------"); if (a!=null){ for (int i=0;i<a.size();i++){ Object[] obj=(Object[])a.get(i); System.out.println((String)obj[0]+" "+(String)obj[1]); } } System.out.println("-------------"); } }
文件HibernateOperation.java
package hibernate.wizard.chapter1; import org.hibernate.*; import org.hibernate.cfg.*; //引入Hibernate的jar包 public class HibernateOperation{ public static final SessionFactory sessionFactory; //静态变量,用于产生Session作为数据库连接 static { try { //从hibernate.cfg.xml创建全局SessionFactory sessionFactory= new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { //创建SessionFactory失败,抛出异常 System.err.println ("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } //静态初始化过程,产生全局SessionFactory(相当于产生数据库连接的工厂) //该静态初始化过程仅当JVM加载该类时执行一次 public static final ThreadLocal session = new ThreadLocal(); public static Session currentSession() throws HibernateException { Session s= (Session)session.get(); // 打开一个新的 Session if (s== null) { s= sessionFactory.openSession(); session.set(s); } return s; } public static void closeSession() throws HibernateException { Session s= (Session)session.get(); if (s != null) s.close(); //关闭session session.set(null); } }
程序运行结果如图1-3所示。
图1-3 例1-2运行结果
通过例1-2可以看出Hibernate进行数据库操作时,其直接操作对象是持久化类的,而不是使用SQL语句直接操作数据库,如进行插入、更新、删除等操作时,都是对对象进行操作。当然,若究其根源,Hibernate的内核最终还是会根据映射文件以及持久化类的定义,通过生成SQL语句来执行各种数据库操作,但那已经不是开发人员关心的事情了。开发人员只需要关注业务逻辑,对持久化类及其关系进行定义,其他的就交给Hibernate去做吧!
从例1-2的程序代码中程序员可以了解到以下几个要点:
● new Configuration().configure().buildSessionFactory():该语句用于初始化一个SessionFactory,采用默认的设置,即到类的根目录上(WEB-INF/classes目录)去寻找hibernate.cfg.xml文件。
关于hibernate.cfg.xml文件的讲解可参见第4章。
● sessionFactory.openSession():该语句用于创建一个Session示例。
● session.beginTransaction():该语句用于开始一个事务。
● session.save(持久化类的一个示例):该语句用于将一个持久化类的示例插入到数据库中。
● session.update(持久化类的一个示例)该语句用于将一个持久化类的示例更新到数据库中。
● session.delet(持久化类的一个示例):该语句用于将一个持久化类的示例所对应的数据库记录删除。
● tx.commit():该语句用于提交一个事务。
● session.close():该语句用于关闭一个Session示例。
● HibernateOperation.sessionFactory.close():该语句用户关闭一个SessionFactory。
以上便是一个最简单的Hibernate应用的工作流程,总结如下:
● 创建SessionFactory(通常一次性创建);
● 创建Session;
● 创建事务Transaction(如果需要事务的话);
● 执行持久化操作(查询、添加、修改、删除等等);
● 提交事务(如果需要事务的话);
● 关闭Session。
● 关闭SessionFactory(通常一次性关闭)。
对于Configuration、SessionFactory、Session、Transaction等Hibernate的核心接口的介绍请参见第2章。
【提示】
SessionFactory是Hibernate的一个核心接口,读者可以将它简单地理解为一个提供数据库连接的工厂,它是线程安全的,只需在JVM中示例化一个该类的对象即可,从这个工厂可以源源不断地产生程序需要的Session示例(可以简单地理解为一个数据库连接,实际上一个Session示例提供了包括数据库连接之外的许多资源)。Session对象不是线程安全的,需要将它与一个ThreadLocal示例绑定,以保证其线程安全性(可参考HibernateOperation工具类的实现)。Session对象执行数据库的保存、修改、删除、查询等操作。Session对象需要在数据库操作完成之后关闭。
1.4.3 两种技术的比较
通过例1-1和例1-2的学习,我们来比较一下JDBC与Hibernate进行数据库操作的异同。
相同点
● 两者都是Java的数据库操作中间件。
● 两者对于数据库进行直接操作的对象都不是线程安全的,都需要及时关闭。
● 两者都可以对数据库的更新操作进行显式的事务处理。
不同点
● 使用的SQL语言不同:JDBC使用的是基于关系型数据库的标准SQL语言,Hibernate使用的是HQL(Hibernate Query Language)语言。
● 操作的对象不同:JDBC操作的是数据,将数据通过SQL语句直接传送到数据库中执行,Hibernate操作的是持久化对象,由Hibernate底层将持久化对象中的数据更新到数据库中。
● 数据状态不同:JDBC操作的数据是“瞬时”的,变量的值无法与数据库中的值保持一致,而Hibernate操作的数据是可持久的,即持久化对象的数据属性的值是可以跟数据库中的值保持一致的。
1.5 小结
面向对象理论的发展使现代的软件开发领域被类、对象、关联等先进的概念迅速占领,关系型数据库的存取也逐渐向其靠拢。对象持久化技术是目前面向对象软件开发过程中非常重要的领域,无论是JDBC方式还是主动域对象模式,都是把对象以及对象之间的关联关系进行持久化的重要手段。
本书将要介绍的Hibernate工作在应用层和数据库层之间,是一种将对象持久化到关系型数据库中的轻量级数据库中间件。它为开发人员呈现了便捷的模式和方法,屏蔽了底层数据库的差异和复杂,使开发人员可以将绝大部分精力放在业务逻辑上,以面向对象的视角完成开发。
1.6 习题
1.试解释对象持久化的概念。
2.试解释对象-关系映射的概念,并重点理解为什么需要对象-关系映射。
3.试比较JDBC和Hibernate的相同点与不同点。
4.请编写一个使用JDBC技术对数据库进行操作的应用程序,重点理解JDBC的SQL语句执行机制与事务处理机制。
5.请仿照例1-2编写一个使用Hibernate技术对数据库进行能够操作的应用程序,重点理解Hibernate应用的工作流程与事务处理机制。