资源描述
第三十四章:Hibernate基础 ITJob就业培训第三十四章:Hibernate基础学习目标n 理解ORM机制n 理解Hibernate的工作原理n Hibernate的配置和对象-映射文件n 理解对象持久化Hibernate简介Hibernate是Java应用和关系数据库之间的桥梁,它负责Java 对象关系数据之间的映射。Hibernate 内部封装了通过 JDBC 访问数据库的操作,向上层应用提供了面向对象的数据访问API。在Java 应用中使用Hibernate包含以下步骤。(1) 创建Hibernate的配置文件。 (2) 创建持久化类。 (3) 创建对象-关系映射文件。 (4) 通过Hibernate API编写访问数据库的代码。建立简单的Hibernate应用 本章通过一个简单的例子 customerApp 应用,演示如何运用 Hibernate 来访问关系数据库。customerApp 应用的功能非常简单:通过 Hibernate 保存、更新、删除、加载以及查询 Customer对象。创建 Hibernate的配置文件Hibernate 从其配置文件中读取和数据库连接有关的信息,这个配置文件应该位于应用的 classpath 中。Hibernate 的配置文件有两种形式:一种是 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:/localhost:3306/SAMPLEB hibernate.connection.username=root hibernate.connection.password=1234 hibernate.show_sql=true以上hibernate.properties文件包含了一系列属性及其属性值,Hibernate将根据这些属性来连接数据库,本例为连接 MySQL 数据库的配置代码。下表对以上 hibernate.properties文件中的所有属性做了描述。属性描述hibernate.dialectSQL指定数据库使用的方言hibernate.connection.driver_class指定数据库的驱动程序hibernate.connection.urlURL指定连接数据库的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方言。创建持久化类持久化类是指其实例需要被 Hibernate 持久化到数据库中的类。持久化类通常都是域模型中的实体域类。持久化类符合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; 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 com.itjob.jiaowu.hibernate.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 类型。在Customer类中没有引入任何Hibernate API,Customer类不需要继承Hibernate的类,或实现Hibernate的接口,这提高了持久化类的独立性。如果日后要改用其他的ORM产品,比如由Hibernate改为OJB,不需要修改持久化类的代码。创建数据库 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类型,参见下表。字段名SQL类型说明IDBIGINT整数,占8字节,取值范围为:-263 263-1NAMEVARCHAR变长字符串,占0 255个字节SEXCHAR定长字符串,占 0 255个字节IS_MARRIEDBIT布尔类型DESCRIPTIONTEXT长文本数据,占 0 65535 255字节。如果字符串长度小于255 ,可以用VARCHAR或 CHAR类型来表示。如果字符串长度大于255 ,可以定义为TEXT类型。IMAGEBLOB二进制长数据,占 0 65535字节,BLOB是 Binary Large Object的缩写。IMAGE在本例中, 字段用来存放图片数据BIRTHDAYDATE代表日期,格式为“YYYY-MM-DD”REGISTERED_TIMETIMESTAMP代表日期和时间,格式为“YYYYMMDDHHMMSS ”创建对象-关系映射文件Hibernate采用XML格式的文件来指定对象和关系数据之间的映射。在运行时,Hibernate将根据这个映射文件来生成各种 SQL 语句。在本例中,将创建一个名为 Customer.hbm.xml的文件,它用于把 Customer 类映射到 CUSTOMERS 表,这个文件应该和 Customer.class 文件存放在同一个目录下。以下为Customer.hbm.xml文件的源代码。 映射文件的文档类型定义(DTD)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软件包的srcnetsfhibernate目录下也提供了 hibernate-mapping-2.0.dtd文件。在这个文件中,描述顶层元素的代码如下: 描述顶层元素的子元素的代码如下:元素是对象-关系映射文件的根元素,其他元素(即以上DTD 代码中括号以内的元素,如子元素)必须嵌入在元素以内。在元素中又嵌套了好多子元素。在以上DTD 代码中,还使用了一系列的特殊符号来修饰元素,下表描述了这些符号的作用。在创建自己的对象-关系映射文件时,如果不熟悉某种元素的语法,可以参考DTD 文件。DTD中特殊符号的作用符号含义无符号该子元素在父元素内必须存在且只能存在一次。+该子元素在父元素内必须存在,可以存在一次或者多次。*该子元素在父元素内可以不存在,或者存在一次或者多次。它是比较常用的符号。?该子元素在父元素内可以不存在,或者只存在一次。它是比较常用的符号。表 2-3 可以看出,在元素中,、和等子元素可以不存在,或者存在一次或者多次;在元素中,子元素必须存在且只能存在一次,元素可以不存在,或者存在一次或者多次。 此外,在映射文件中,父元素中的各种子元素的定义必须符合特定的顺序。例如根据元素的DTD 可以看出,必须先定义子元素,再定义子元素,以下映射代码颠倒了和子元素的位置: 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)把Customer持久化类映射到CUSTOMERS表Customer.hbm.xml文件用于映射 Customer 类。如果需要映射多个持久化类,那么既可以在同一个映射文件中映射所有类,也可以为每个类创建单独的映射文件,映射文件和类同名,扩展名为“hbm.xml”。后一种做法更值得推荐,因为在团队开发中,这有利于管理和维护映射文件。 元素指定类和表的映射,它的name属性设定类名,table属性设定表名。以下代码表明和Customer 类对应的表为CUSTOMERS表: 如果没有设置元素的table属性,Hibernate将直接以类名作为表名,也就是说,默认情况下,与mypack.Customer 类对应的表为Customer 表。 元素包含一个子元素以及多个子元素。子元素设定持久化类的OID 和表的主键的映射。以下代码表明 Customer 类的 id属性和 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。例如以下代码表明不允许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属性: 那么Hibernate在持久化一个Customer 对象时,不会先检查它的 name属性是否为 null而是直接通过 JDBC API 向 CUSTOMERS 表插入相应的数据,由于 CUSTOMERS 表的NAME 字段设置了not null约束,因此数据库会抛出错误: 708 ERROR JDBCExceptionReporter:58 - General error, message from server: Column NAME cannot be null4元素的column属性 元素的 column 属性指定与类的属性映射的表的字段名。以下代码表明和address属性对应的字段为ADDRESS字段: 如果没有设置元素的 column属性,Hibernate将直接以类的属性名作为字段名,也就是说,默认情况下,与Customer 类的 address属性对应的字段为 address字段。 元素还可以包括子元素,它和元素的 column属性一样,都可以设定与类的属性映射的表的字段名。以下两种设置方式是等价的: 或者 元素的子元素比 column 属性提供更多的功能,它可以更加详细的描述表的字段。例如以下子元素指定 CUSTOMERS 表中的 NAME 字段的 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.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(tomyahoo.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 bytein.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接口来访问数据库。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对象有任何影响。访问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(); 下图为正常执行数据库事务(即没有发生异常)的时序图。BusinessService 类提供了保存、删除、查询和更新 Customer 对象的各种方法。BusinessService类的main()方法调用test()方法,test()方法又调用以下方法: 1saveCustomer()方法 该方法调用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) ,1234,55556666,Shanghai,M,0,I am very honest., values(1,Tom,tomyahoo.com?,1980-05-06,null) 在test()方法中并没有设置Customer 对象的 id属性,Hibernate会根据映射文件的配置,采用increment标识符生成器自动以递增的方式为 OID 赋值。在 Customer.hbm.xml文件中相关的映射代码如下: 在test()方法中也没有设置Customer 对象的 registeredTime属性,因此在以上 insert语句中,REGISTERED_TIME 字段的值为 null。但由于 REGISTERED_TIME 字段的 SQL 类型为 TIMESTAMP 类型,如果 insert 语句没有为 TIMESTAMP 类型的字段赋值,底层数据库会自动把当前的系统时间赋值给TIMESTAMP类型的字段。因此,执行完以上 insert语句后,REGISTERED_TIME 字段的值并不为 null,而是插入该记录时的系统时间。 2findAllCustomers()方法 该方法调用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=Tomyahoo.comADDRESS=Beijing 尽管只有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 信息; 当 customerApp 应用作为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()+的个人信息-+); out.println(ID: +customer.getId()+); out.println(口令: +customer.getPassword()+); out.println(E-Mail: +customer.getEmail()+); out.println(电话: +customer.getPhone()+); ou
展开阅读全文