在没有正式开始旅行之前,让我们区分几个名词。POJO:在 Hibernate 中代表包含 Seter、Geter 这些最基本操作的值对象。而 BO:代表包含一些业务逻辑的值对象,它的作用域很大,也就是说 BO 在充当持久类的同时可以传到 UI 层。PO:代表持久对象,是纳入 Hibernate 管理框架中的,在一定程度上可以和值对象的概念互换,值对象经过 Hibernate 进行处理,就变成了 PO 。Hibernate 配置文件:hibernate.cfg.xml 或 hibernate.properties,不过推荐使用 XML 格式。映射文件 *.hbm.xml:映射文件的作用是将 POJO 与关系型数据库数据相绑定,作为一个桥梁。另外,为数据库中的表进行手工编写映射文件可不是件好差事,幸好开源社区中也有一群同样想法的人,他们开发了 hibernateSynchronizer 映射工具,可到 http://www.binamics.com/hibernatesync/eclipse2.1/ 下载。
好了,现在去 http://www.hibernate.org 下载 Hibernate 的开发包(目前的版本是 2.1.6)。接着打开 Eclipse 2.1,在更新管理器中安装 hibernateSynchronizer。据说 Eclipse 3.0 已内置映射工具,不过我没有试。安装完毕后,新建一个 Web 应用程序 HibernateTest,接着为这个应用程序添加 Hibernate 类库 hibernate-2.1.6/hibernate-2.1/hibernate2.jar 及其依赖类库、数据库连接包,强烈建议把 hibernate-2.1.6/hibernate-2.1/lib 下的所有类库全部加载,如下图:
接着使用 hibernateSynchronizer 来生成 hibernate.cfg.xml 文件,新建——>其他——>Hibernate Configuration File,我使用的是 SQL Server 数据库,各项配置参数见下图:
生成出来的 hibernate.cfg.xml 文件:
在项目的 src 目录下新建四个包,分别是bo、bo.base、bo.mapping、com.dao,具体什么作用,到时候他们都会一一呈献。在开始映射文件前还要做一件事,为应用程序 HibernateTest 配置 hibernateSynchronizer ,我更喜欢自己写 DAO ,所以没有配置 Data Access Objects,其他各项参数如下图:
被映射的表 AutoInfo 结构如下,id为其主键:
新建——>其他——>Hibernate Mapping File,配置好参数再“Refresh”后,选择要映射的表,注意千万不要在 800*600 下映射文件!否则有些按钮不会出现,各项配置参数见下图:
终于、终于,映射文件 AutoInfo.hbm.xml 终于出来了!
<?xml version="1.0"?> <hibernate-mapping package="bo.base"> |
<?xml version="1.0"?> <hibernate-mapping package="bo.base"> |
需要修改一下 AutoInfo.hbm.xml 文件为其定义主键,把:
<property column="id" length="18" name="Id" not-null="true" type="integer" /> |
<property column="id" length="18" name="Id" not-null="true" type="integer" /> |
替换为:
<id name="Id" column="id" type="integer"> <generator class="native"/> </id> |
<id name="Id" column="id" type="integer"> <generator class="native"/> </id> |
再接再厉,为映射文件生成 POJO。修改 hibernate.cfg.xml 文件在 </session-factory> 标签的上面加上刚才映射的文件 <mapping resource="bo/mapping/AutoInfo.hbm.xml" />。接着在“包资源管理器”中点击 AutoInfo.hbm.xml 右键,Hibernate Synchronizer——>Synchronize Files。再看看 src 中的包:
在上一部分我只讲到如何通过数据库中的表生成映射文件和 POJO。在这一部分中,我将讲解映射文件。
先看看这张表:
在现实的车辆管理系统中,绝对不会把车辆信息和拥有人信息放在一张表中。应该是“一个拥有者”拥有一或多个“车辆”。来看看分解后的表,PEOPLE 表,设置 OWNER_ID 为 主键:
AUTO_INFO 表,设置 AUTO_ID 为 主键:
为两表配置主、外键关系,设置 PEOPLE 表为主表:
为两表配置主、外键关系,设置 PEOPLE 表为主表:
为两表配置主、外键关系,设置 PEOPLE 表为主表:
打开 Eclipse,为这两张表生成映射文件。
AutoInfo.hbm.xml:
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
People.hbm.xml:
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
以 AutoInfo.hbm.xml 进行详细分析:
DOCTYPE 定义了这个 XML 文件规范 DTD,可以通过后面的 URL 获取,或者在 Hibernate 的 CLASSPATH 中获取。
hibernate-mapping package="bo" 定义了POJO所存在的包“bo”。
class name="AutoInfo" table="AUTO_INFO" 定义了 POJO 的名称“AutoInfo”和数据库中的表名“AUTO_INFO”。
id column="AUTO_ID" name="Id" type="integer" 定义了数据库表中的主键字段,为相应的 POJO 的每个实例包含唯一标识。column 为数据库表字段名,name 标识属性名字,type 说明在 POJO 中的类型。另外,还有一个重要标识:unsaved-value,将会在以后用到。
Generator 为每个 POJO 的实例提供唯一标识。一般情况,我们使用“native”。class 表示采用由生成器接口net.sf.hibernate.id.IdentifierGenerator 实现的某个实例,其中包括:
“assigned”
主键由外部程序负责生成,在 save() 之前指定一个。
“hilo”
通过hi/lo 算法实现的主键生成机制,需要额外的数据库表或字段提供高位值来源。
“seqhilo”
与hilo 类似,通过hi/lo 算法实现的主键生成机制,需要数据库中的 Sequence,适用于支持 Sequence 的数据库,如Oracle。
“increment”
主键按数值顺序递增。此方式的实现机制为在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的时候将此值加1作为主键。这种方式可能产生的问题是:不能在集群下使用。
“identity”
采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL 中的主键生成机制。
“sequence”
采用数据库提供的 sequence 机制生成主键。如 Oralce 中的Sequence。
“native”
由 Hibernate 根据使用的数据库自行判断采用 identity、hilo、sequence 其中一种作为主键生成方式。
“uuid.hex”
由 Hibernate 基于128 位 UUID 算法 生成16 进制数值(编码后以长度32 的字符串表示)作为主键。
“uuid.string”
与uuid.hex 类似,只是生成的主键未进行编码(长度16),不能应用在 PostgreSQL 数据库中。
“foreign”
使用另外一个相关联的对象的标识符作为主键。
property column="LICENSE_PLATE" length="20" name="LicensePlate" not-null="false" type="string"
property column="LICENSE_PLATE" length="20" name="LicensePlate" not-null="false" type="string"
property column="LICENSE_PLATE" length="20" name="LicensePlate" not-null="false" type="string"
定义了 POJO 中的属性,分别表示数据库表中的字段、长度、POJO 属性名称以及类型、非空状态。
many-to-one class="People" name="OwnerNo" not-null="true" column name="OWNER_NO" 定义了一个持久化对象与另一个持久化对象的关系,这种关联模型是多对一的关联。应用在车辆管理系统中,代表着多台车辆由一个拥有者拥有。分别表示关联类的名字(由反射机制得到类型)、属性名称、非空状态、字段名。
分析完 AutoInfo.hbm.xml 后,再来看看 People.hbm.xml。大部分内容都已经在前面出现过,不同的地方是:
<set inverse="true" name="AutoInfoSet"> |
<set inverse="true" name="AutoInfoSet"> |
在车辆管理系统中,代表着一个拥有者拥有多台车辆。以 java.util.Set 类型表示。 inverse 用于标识双向关联中的被动方一端。inverse=false 的一方(主控方)负责维护关联关系;在车辆管理系统中, AutoInfo 作为主控方,应该把它设为“true”。这就好比你(被动方 one)在某个聚会上散发了许多名片,但是有可能你不清楚接收者(主动方 many)的具体背景;这个不要紧,接收者在必要的时候会和你联系就是了(主动维护关系)。
另外在 set 节点的属性中还有一个重要标识:级联(cascade)关系,指明哪些操作(insert、update、delete)会从主控方对象级联到关联的对象。
可选值:
all: save()、saveOrUpdate()、update()、delete() 均进行级联操作。
none: 所有情况下均不进行级联操作。
save-update: 在执行 save()、saveOrUpdate()、update() 时进行级联操作。
delete: 只在执行 delete() 时进行级联操作。
级联(cascade)在 Hibernate 映射关系中是个非常重要的概念。它指的是当主控对象调用 save-update 或 delete 方法时,是否同时对关联对象(被动方)进行 save-update 或 delete 。在这个映射文件中,当拥有者(People)被更新或者删除时,其所关联的车辆(AutoInfo)可以被修改或删除,所以应该把级联关系设置为cascade=”all”。
<set inverse="true" name="AutoInfoSet"> |
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
为两表配置主、外键关系,设置 PEOPLE 表为主表:
<id name="Id" column="id" type="integer"> <generator class="native"/> </id> |
<property column="id" length="18" name="Id" not-null="true" type="integer" /> |
<?xml version="1.0"?> <hibernate-mapping package="bo.base"> |
在没有正式开始旅行之前,让我们区分几个名词。POJO:在 Hibernate 中代表包含 Seter、Geter 这些最基本操作的值对象。而 BO:代表包含一些业务逻辑的值对象,它的作用域很大,也就是说 BO 在充当持久类的同时可以传到 UI 层。PO:代表持久对象,是纳入 Hibernate 管理框架中的,在一定程度上可以和值对象的概念互换,值对象经过 Hibernate 进行处理,就变成了 PO 。Hibernate 配置文件:hibernate.cfg.xml 或 hibernate.properties,不过推荐使用 XML 格式。映射文件 *.hbm.xml:映射文件的作用是将 POJO 与关系型数据库数据相绑定,作为一个桥梁。另外,为数据库中的表进行手工编写映射文件可不是件好差事,幸好开源社区中也有一群同样想法的人,他们开发了 hibernateSynchronizer 映射工具,可到 http://www.binamics.com/hibernatesync/eclipse2.1/ 下载。
好了,现在去 http://www.hibernate.org 下载 Hibernate 的开发包(目前的版本是 2.1.6)。接着打开 Eclipse 2.1,在更新管理器中安装 hibernateSynchronizer。据说 Eclipse 3.0 已内置映射工具,不过我没有试。安装完毕后,新建一个 Web 应用程序 HibernateTest,接着为这个应用程序添加 Hibernate 类库 hibernate-2.1.6/hibernate-2.1/hibernate2.jar 及其依赖类库、数据库连接包,强烈建议把 hibernate-2.1.6/hibernate-2.1/lib 下的所有类库全部加载,如下图:
接着使用 hibernateSynchronizer 来生成 hibernate.cfg.xml 文件,新建——>其他——>Hibernate Configuration File,我使用的是 SQL Server 数据库,各项配置参数见下图:
生成出来的 hibernate.cfg.xml 文件:
在项目的 src 目录下新建四个包,分别是bo、bo.base、bo.mapping、com.dao,具体什么作用,到时候他们都会一一呈献。在开始映射文件前还要做一件事,为应用程序 HibernateTest 配置 hibernateSynchronizer ,我更喜欢自己写 DAO ,所以没有配置 Data Access Objects,其他各项参数如下图:
被映射的表 AutoInfo 结构如下,id为其主键:
新建——>其他——>Hibernate Mapping File,配置好参数再“Refresh”后,选择要映射的表,注意千万不要在 800*600 下映射文件!否则有些按钮不会出现,各项配置参数见下图:
终于、终于,映射文件 AutoInfo.hbm.xml 终于出来了!
<?xml version="1.0"?> <hibernate-mapping package="bo.base"> |
<?xml version="1.0"?> <hibernate-mapping package="bo.base"> |
需要修改一下 AutoInfo.hbm.xml 文件为其定义主键,把:
<property column="id" length="18" name="Id" not-null="true" type="integer" /> |
<property column="id" length="18" name="Id" not-null="true" type="integer" /> |
替换为:
<id name="Id" column="id" type="integer"> <generator class="native"/> </id> |
<id name="Id" column="id" type="integer"> <generator class="native"/> </id> |
再接再厉,为映射文件生成 POJO。修改 hibernate.cfg.xml 文件在 </session-factory> 标签的上面加上刚才映射的文件 <mapping resource="bo/mapping/AutoInfo.hbm.xml" />。接着在“包资源管理器”中点击 AutoInfo.hbm.xml 右键,Hibernate Synchronizer——>Synchronize Files。再看看 src 中的包:
在上一部分我只讲到如何通过数据库中的表生成映射文件和 POJO。在这一部分中,我将讲解映射文件。
先看看这张表:
在现实的车辆管理系统中,绝对不会把车辆信息和拥有人信息放在一张表中。应该是“一个拥有者”拥有一或多个“车辆”。来看看分解后的表,PEOPLE 表,设置 OWNER_ID 为 主键:
AUTO_INFO 表,设置 AUTO_ID 为 主键:
为两表配置主、外键关系,设置 PEOPLE 表为主表:
为两表配置主、外键关系,设置 PEOPLE 表为主表:
为两表配置主、外键关系,设置 PEOPLE 表为主表:
打开 Eclipse,为这两张表生成映射文件。
AutoInfo.hbm.xml:
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
People.hbm.xml:
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
以 AutoInfo.hbm.xml 进行详细分析:
DOCTYPE 定义了这个 XML 文件规范 DTD,可以通过后面的 URL 获取,或者在 Hibernate 的 CLASSPATH 中获取。
hibernate-mapping package="bo" 定义了POJO所存在的包“bo”。
class name="AutoInfo" table="AUTO_INFO" 定义了 POJO 的名称“AutoInfo”和数据库中的表名“AUTO_INFO”。
id column="AUTO_ID" name="Id" type="integer" 定义了数据库表中的主键字段,为相应的 POJO 的每个实例包含唯一标识。column 为数据库表字段名,name 标识属性名字,type 说明在 POJO 中的类型。另外,还有一个重要标识:unsaved-value,将会在以后用到。
Generator 为每个 POJO 的实例提供唯一标识。一般情况,我们使用“native”。class 表示采用由生成器接口net.sf.hibernate.id.IdentifierGenerator 实现的某个实例,其中包括:
“assigned”
主键由外部程序负责生成,在 save() 之前指定一个。
“hilo”
通过hi/lo 算法实现的主键生成机制,需要额外的数据库表或字段提供高位值来源。
“seqhilo”
与hilo 类似,通过hi/lo 算法实现的主键生成机制,需要数据库中的 Sequence,适用于支持 Sequence 的数据库,如Oracle。
“increment”
主键按数值顺序递增。此方式的实现机制为在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的时候将此值加1作为主键。这种方式可能产生的问题是:不能在集群下使用。
“identity”
采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL 中的主键生成机制。
“sequence”
采用数据库提供的 sequence 机制生成主键。如 Oralce 中的Sequence。
“native”
由 Hibernate 根据使用的数据库自行判断采用 identity、hilo、sequence 其中一种作为主键生成方式。
“uuid.hex”
由 Hibernate 基于128 位 UUID 算法 生成16 进制数值(编码后以长度32 的字符串表示)作为主键。
“uuid.string”
与uuid.hex 类似,只是生成的主键未进行编码(长度16),不能应用在 PostgreSQL 数据库中。
“foreign”
使用另外一个相关联的对象的标识符作为主键。
property column="LICENSE_PLATE" length="20" name="LicensePlate" not-null="false" type="string"
property column="LICENSE_PLATE" length="20" name="LicensePlate" not-null="false" type="string"
property column="LICENSE_PLATE" length="20" name="LicensePlate" not-null="false" type="string"
定义了 POJO 中的属性,分别表示数据库表中的字段、长度、POJO 属性名称以及类型、非空状态。
many-to-one class="People" name="OwnerNo" not-null="true" column name="OWNER_NO" 定义了一个持久化对象与另一个持久化对象的关系,这种关联模型是多对一的关联。应用在车辆管理系统中,代表着多台车辆由一个拥有者拥有。分别表示关联类的名字(由反射机制得到类型)、属性名称、非空状态、字段名。
分析完 AutoInfo.hbm.xml 后,再来看看 People.hbm.xml。大部分内容都已经在前面出现过,不同的地方是:
<set inverse="true" name="AutoInfoSet"> |
<set inverse="true" name="AutoInfoSet"> |
在车辆管理系统中,代表着一个拥有者拥有多台车辆。以 java.util.Set 类型表示。 inverse 用于标识双向关联中的被动方一端。inverse=false 的一方(主控方)负责维护关联关系;在车辆管理系统中, AutoInfo 作为主控方,应该把它设为“true”。这就好比你(被动方 one)在某个聚会上散发了许多名片,但是有可能你不清楚接收者(主动方 many)的具体背景;这个不要紧,接收者在必要的时候会和你联系就是了(主动维护关系)。
另外在 set 节点的属性中还有一个重要标识:级联(cascade)关系,指明哪些操作(insert、update、delete)会从主控方对象级联到关联的对象。
可选值:
all: save()、saveOrUpdate()、update()、delete() 均进行级联操作。
none: 所有情况下均不进行级联操作。
save-update: 在执行 save()、saveOrUpdate()、update() 时进行级联操作。
delete: 只在执行 delete() 时进行级联操作。
级联(cascade)在 Hibernate 映射关系中是个非常重要的概念。它指的是当主控对象调用 save-update 或 delete 方法时,是否同时对关联对象(被动方)进行 save-update 或 delete 。在这个映射文件中,当拥有者(People)被更新或者删除时,其所关联的车辆(AutoInfo)可以被修改或删除,所以应该把级联关系设置为cascade=”all”。
<set inverse="true" name="AutoInfoSet"> |
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
<?xml version="1.0"?> <hibernate-mapping package="bo"> |
为两表配置主、外键关系,设置 PEOPLE 表为主表:
<id name="Id" column="id" type="integer"> <generator class="native"/> </id> |
<property column="id" length="18" name="Id" not-null="true" type="integer" /> |
<?xml version="1.0"?> <hibernate-mapping package="bo.base"> |
在本部分中,才真正开始对 AUTO_INFO 和 PEOPLE 表进行操作。
要让 Hibernate 跑起来,还要了解其中几个关键对象:
net.sf.hibernate.cfg.Configuration 的实例负责管理 Hibernate 配置信息,比如数据库连接、数据库 dialect,还有最重要的映射文件初始化工作。
程序为了得到 Session 实例,必须先要得到它的工厂 net.sf.hibernate.SessionFactory,SessionFactory 的实例由 Configuration 构造。
net.sf.hibernate.Session 是一切数据库操作的基础,和 JDBC 的 Connection 意义一样,Session 实例由 SessionFactory 获得。
net.sf.hibernate.Transaction 的实例由 Session 获得。Hibernate 不具备事务管理能力,Hibernate 将其委托给底层的 JDBC 或者 JTA,以实现事务管理和调度功能。在本文中使用 JDBC 事务管理。
把以上的知识串起来吧:
Configuration cfg = new Configuration().configure();
SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession();
Transaction tx = session.beginTransaction();
|
Configuration cfg = new Configuration().configure();
SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession();
Transaction tx = session.beginTransaction();
|
程序第一幕马上出场。
有位叫张三的人,他买了辆车。由于是第一次买车,进入车辆管理系统后要对AUTO_INFO 和 PEOPLE 表都进行 insert 操作。
形成的一对多(one-to-many)关系保存如下:
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
分别设置好 AutoInfo、People 对象属性后,调用 Session.save() 方法保存,然后事务提交,最后关闭 Session。好了,看看数据库吧,一切都已保存好了。
程序第二幕出场。
张三后来做生意,自己经营得很好,打算再买辆车跑运输。对于第二次买车,车辆管理系统的 PEOPLE 表原本已经记录了他的基本信息,遂不对 PEOPLE 表操作。只向 AUTO_INFO表 insert 一条车辆记录即可。
形成的一对多(one-to-many)关系保存如下:
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
session.find() 方法返回一个 List 接口的实例,里面封装着 PO,其中的 “from People where OWNER_ID=1” 就是大名鼎鼎的 HQL(Hibernate Query Language) 了,是不是很像 SQL 呢?PEOPLE 表和 AUTO_INFO 表存在一对多关系,也就需要 People 对象来持有多个 AutoInfo 对象(以 Set 接口的实例封装,参看People类源代码),再通过 people.getAutoInfoSet().add(ai) 为 AUTO_INFO 表添加一条新纪录。好了,执行完以后,再检查数据库吧。
这段代码
people = (People) session.find("from People where OWNER_ID=1").get(0); |
可以和
people =(People) session.load(People.class,new Integer(1)); |
互换。 在本例中 List 接口实例 size() 为 1,即其中只有一个 PO;而 session.load() 是根据持久对象和主键来返回相应 PO,也只是单个。所以这两种方式返回的都是相同 PO。采用哪种方式由你决定。
到这里,也许你会有这样的想法:“应该可以直接向 AUTO_INFO 表插入记录,不通过 People 对象中转,像写 SQL 一样 Easy。” 错了!以前直接写 SQL 是可以办到的,不过现在我们用的可是 Hibernate ,一切都要以对象行事,看见 ai.setOwnerNo(people) 了吗?传入参数是个 People 对象实例,不是简单的字段喔。
程序第三幕出场。
呵呵,这个第三幕是最简单的了,第一种一对多(one-to-many)的查询:
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
到了年底,查查张三一共有多少辆车。”select ai from AutoInfo as ai where ai.OwnerNo.Id=1”,这句 HQL 要找的是 AutoInfo 对象,而传统 SQL 这么写:“select p.name,p.address,ai.license_plate from people p,auto_info ai where p.owner_id=1”。由于 AUTO_INFO 表是“many”表,而 PEOPLE 表是“one”表,我的做法是以“many”表为基础返回它的多个 PO,其中再持有“one”表的信息。“ai.OwnerNo.Id=1”就是说通过主键参数为“1”的 PEOPLE 表记录来取出相应 AUTO_INFO 表记录。
对于一对多(one-to-many)关系,还有第二种查询方式:
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { AutoInfo ai = new AutoInfo(); |
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { AutoInfo ai = new AutoInfo(); |
people = (People) session.load(People.class, new Integer(1)) 取出单个 People 对象,其中持有以 Set 封装的若干 AutoInfo 对象实例,Iterator iterator = people.getAutoInfoSet().iterator() 把 Set 立即转化为 Iterator ,最后用 while (iterator.hasNext()) 循环取出其中的车牌号码。
第一种一对多(one-to-many)的查询实际上我把它转换成了多对一(many -to- one)的查询。这种转换后有两个缺点:一、由 HQL 转为 SQL 输出时,打印的 SQL 条数多于直接一对多(one-to-many)关系查询;二、其中的 People 对象存在着大量冗余(只需要一个实例,结果取出了 List.size() 个相同的实例)。我们知道,数据库的性能是有限的,构造对象的代价是高昂的,所以应尽量减少不必要的性能开销。
虽然我个人不建议把一对多(one-to-many)查询转换成多对一(many -to- one)查询,但事实上有些开发团队却乐意采用,即使他们知道性能有略微的降低。在还没有更深入研究之前,各位看官有何看法呢?
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { AutoInfo ai = new AutoInfo(); |
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { List list; |
Configuration cfg = new Configuration().configure();
SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession();
Transaction tx = session.beginTransaction();
|
市场是无情的,机遇和危机无处不在。张三在经历过生意红火之后,接下来的一年内生意场上连连告负,不得不把自己的摊子收缩一下。这第一件事要把跑运输的车卖掉,就是那辆牌照为“A00002”的。
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { AutoInfo ai; |
为什么要从 People 对象中移除某个 AutoInfo 对象?
问得好!传统 JDBC 程序可以直接删除以“A00002”为条件查询出的记录,这样没有问题。但如果在 Hibernate 中用同样的方式直接删除,会引起不小的麻烦:
net.sf.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): 2, of class: bo.AutoInfo |
造成无法删除的原因是PEOPLE 和 AUTO_INFO 表存在着一对多(one-to-many)关系,想要从 AUTO_INFO 删除一条记录,就必须用 people.getAutoInfoSet().remove(ai) 方法为 People 移除以“A00002”为条件查询出的AutoInfo 对象,才能真正删除该 AutoInfo 对象。
从张三的失落中回过头来,这次 Hibernate 之旅也即将结束了。最后要体验的是删除 PEOPLE 表及其关联的 AUTO_INFO 表。
package com.dao; import java.util.*; import net.sf.hibernate.*; import bo.*; public class Test { People people; |
Hibernate 的优势又一次体现出来。我们只需把一对多(one-to-many)关系中“one”这方删除,与之相关联的所有其他记录会一并删除。
最后,通过这次旅程,也算把 Hibernate 的特性体验了一把。作为一种 O/R Mapping 实现,它是很优秀的,希望我们都可以用好它。
在车辆管理系统中,代表着一个拥有者拥有多台车辆。以 java.util.Set 类型表示。 inverse 用于标识双向关联中的被动方一端。inverse=false 的一方(主控方)负责维护关联关系;在车辆管理系统中, AutoInfo 作为主控方,应该把它设为“true”。这就好比你(被动方 one)在某个聚会上散发了许多名片,但是有可能你不清楚接收者(主动方 many)的具体背景;这个不要紧,接收者在必要的时候会和你联系就是了(主动维护关系)。 |
红色标识的句子容易让人产生歧异,好像是在说把 AtuoInfo 设置成“true”了。应改为:
在车辆管理系统中,AtuoInfo 作为主控方,应该在 People 中设置 inverse =“true”。 |
沿着思路往下走,你也许会问:什么才叫“主动维护关系”?不妨看看下面的代码(摘自 《我的 O/R Mapping 之旅(三)》):
AutoInfo ai=new AutoInfo(); People people=new People(); public void DoTest() { try { Configuration cfg = new Configuration().configure(); SessionFactory sessions = cfg.buildSessionFactory(); Session session = sessions.openSession(); Transaction tx = session.beginTransaction(); ai.setLicensePlate("A00001"); ai.setOwnerNo(people); people.setAddress("中国"); people.setName("张三"); people.addToAutoInfoSet(ai); session.save(people); tx.commit(); session.close(); } catch (Exception e) { System.out.println(e); } } |
把“ai.setOwnerNo(people)”注解了试试,由于 AutoInfo 没有主动维护关系,导致 AUTO_INFO 表中 OWNER_NO 字段为“Null”。自然 AutoInfo 与 Poople 就不存在任何联系了。
人类的求知欲很强烈!
为什么非要用 AutoInfo 作为主控方?People 作主控方不行?好吧,为 People.hbm.xml 删除inverse=”true”,再运行以上程序,其实也能保存,只是多了一条SQL:“update auto_info set OWNER_NO=? where AUTO_ID=?”,这就是 AutoInfo 被动地修改和 People 的联系。多执行一次 SQL 意味着多了一些开销,这是对性能不利的!
《我的 O/R Mapping 之旅(三)》,有一段对张三第二次买车的程序和描述:
AutoInfo ai = new AutoInfo(); People people = new People(); public void DoTest() { try { Configuration cfg = new Configuration().configure(); SessionFactory sessions = cfg.buildSessionFactory(); Session session = sessions.openSession(); Transaction tx = session.beginTransaction(); people = (People) session .find( "from People where OWNER_ID=1") .get(0); ai.setLicensePlate("A00002"); ai.setOwnerNo(people); people.getAutoInfoSet().add(ai); session.save(people); tx.commit(); session.close(); } catch (Exception e) { System.out.println(e); } } |
到这里,也许你会有这样的想法:“应该可以直接向 AUTO_INFO 表插入记录,不通过 People 对象中转,像写 SQL 一样 Easy。” 错了!以前直接写 SQL 是可以办到的,不过现在我们用的可是 Hibernate ,一切都要以对象行事,看见 ai.setOwnerNo(people) 了吗?传入参数是个 People 对象实例,不是简单的字段喔。 |
这段解释太绝对了,事实上可以直接保存 AutoInfo 对象,而不用通过保存 People 来中转:
AutoInfo ai = new AutoInfo(); People people = new People(); public void DoTest() { try { Configuration cfg = new Configuration().configure(); SessionFactory sessions = cfg.buildSessionFactory(); Session session = sessions.openSession(); Transaction tx = session.beginTransaction(); people = (People) session .find( "from People where OWNER_ID=1") .get(0); ai.setLicensePlate("A00002"); ai.setOwnerNo(people); session.save(ai); tx.commit(); session.close(); } catch (Exception e) { System.out.println(e); } } |
《我的 O/R Mapping 之旅(四)》,删除 PEOPLE 表及其关联的 AUTO_INFO 表时,程序是没有错,不过有更简单的办法来删除:
try { Configuration cfg = new Configuration().configure(); SessionFactory sessions = cfg.buildSessionFactory(); Session session = sessions.openSession(); Transaction tx = session.beginTransaction(); session.delete("from People where OWNER_ID=1"); tx.commit(); session.close(); } catch (Exception e) { System.out.println(e); } |
Hibernate 中,要完成一次操作,可以有多种实现方式,哪种最好,就要靠自己定夺了。
原作者:Rosen Jiang 以及出处: http://blog.csdn.net/rosen