Original Author: 夏昕 本文是由笔者2003 年底一个咨询项目中,为客户做的持久层设计培训 文案整理而来。 其中的内容涉及Hibernate 的使用,以及一部分笔者实际咨询项目中的 经验积累,另一方面,大部分是笔者在Hibernate 的官方论坛中与众多 技术专家交流所得。 既来于斯,则归于斯。希望能聊有所用。 本文并非试图替代Hibernate Reference,相对而言,Hibernate Reference 的编写目的是为开发者提供更简便的条目索引,而本文目标则在于为开 发人员提供一个入门和掌握Hibernate的途径。 本文需结合Hibernate Reference使用。 笔者好友曹晓钢义务组织了Hibernate文档的汉化工作,在此对其辛勤劳作致敬。 中文版Hibernate Reference将被包含在Hibernate下个官方Release中,目前可 通过http://www.redsaga.com获取中文版Hibernate Reference的最新版本。 本文中如果发现问题和错误,请随时联系笔者,以免误导他人。 本文转载不限,不过请保持本文完整。万分感谢!
转载URL:http://blog.csdn.net/x51x/archive/2004/10/08/128522.aspx
Hibernate 开发指南
.......................................................................................................1 准备工作
..........................................................................................................3 构建Hibernate基础代码
...............................................................................3 由数据库产生基础代码
...........................................................................4 Hibernate配置
..............................................................................................15 第一段代码
....................................................................................................17 Hibernate基础语义
......................................................................................19 Configuration ........................................................................................19 SessionFactory
.......................................................................................20 Session
....................................................................................................20 Hibernate高级特性
......................................................................................................22 XDoclet与Hibernate映射
...........................................................................22 数据检索
........................................................................................................31 Criteria Query
...............................................................................31 Criteria查询表达式
................................................................31 Criteria高级特性
....................................................................33 限定返回的记录范围
.............................................................33 对查询结果进行排序
.............................................................33 Hibernate Query Language (HQL)
.........................................34 数据关联
........................................................................................................35 一对一关联
.............................................................................35 一对多关联
.............................................................................37 Ø 单向一对多关系
......................................................37 Ø 双向一对多关系
......................................................42 多对多关联
.............................................................................47 数据访问
........................................................................................................54 PO和VO
...............................................................................................54 关于unsaved-value ...............................................................................57 Inverse和Cascade.
........................................................................59 延迟加载(Lazy Loading)
............................................................59 事务管理.
.......................................................................................................63 基于JDBC的事务管理:
.....................................................................64 基于JTA的事务管理:
.......................................................................65 锁(locking)
.........................................................................................68 悲观锁(Pessimistic Locking)
.......................................68 乐观锁(Optimistic Locking)
..........................................69 Hibernate分页
..........................................................................................73 Cache管理
....................................................................................................75 Session管理
...............................................................................................79 编后赘言
................................................................................................................84 Hibernate Quick Start
准备工作 1. 下载Ant软件包,解压缩(如C:\ant\)。并将其bin目录(如c:\ant\bin)添加到系统 PATH 中。
2. 下载Hibernate、Hibernate-Extension和Middlegen-Hibernate软件包的最新版本。 http://prdownloads.sourceforge.net/hibernate/ 构建Hibernate 基础代码 Hibernate基础代码包括: 1. POJO POJO 在Hibernate 语义中理解为数据库表所对应的Domain Object。这里的POJO 就是所谓的“Plain Ordinary Java Object”,字面上来讲就是无格式普通Java 对象,简 单的可以理解为一个不包含逻辑代码的值对象(Value Object 简称VO)。 一个典型的POJO: public class TUser implements Serializable { private String name; public User(String name) { this.name = name; } /** default constructor */ public User() { } public String getName() { return this.name; } public void setName(String name) { this.name = name; } } 2. Hibernate 映射文件 Hibernate 从本质上来讲是一种“对象-关系型数据映射”(Object Relational Mapping 简称ORM)。前面的POJO在这里体现的就是ORM中Object层的语义, 而映射(Mapping)文件则是将对象(Object)与关系型数据(Relational)相关联 的纽带,在Hibernate中,映射文件通常以“.hbm.xml”作为后缀。 构建Hibernate基础代码通常有以下途径: 1. 手工编写 2. 直接从数据库中导出表结构,并生成对应的ORM文件和Java 代码。 这是实际开发中最常用的方式,也是这里所推荐的方式。 通过直接从目标数据库中导出数据结构,最小化了手工编码和调整的可能性,从而 最大程度上保证了ORM文件和Java 代码与实际数据库结构相一致。 3. 根据现有的Java 代码生成对应的映射文件,将Java 代码与数据库表相绑定。 通过预先编写好的POJO 生成映射文件,这种方式在实际开发中也经常使用,特别 是结合了xdoclet 之后显得尤为灵活,其潜在问题就是与实际数据库结构之间可能 出现的同步上的障碍,由于需要手工调整代码,往往调整的过程中由于手工操作的 疏漏,导致最后生成的配置文件错误,这点需要在开发中特别注意。 结合xdoclet,由POJO 生成映射文件的技术我们将在“高级特性”章节中进行探讨。 由数据库产生基础代码 通过Hibernate官方提供的MiddleGen for Hibernate 和Hibernate_Extension工具包,我 们可以很方便的根据现有数据库,导出数据库表结构,生成ORM和POJO。 1) 首先,将Middlegen-Hibernate软件包解压缩( 如解压缩到C:\Middlegen\ )。 2) 配置目标数据库参数 进入MiddleGen 目录下的\config\database 子目录,根据我们实际采用的数据库打开 对应的配置文件。如这里我们用的是mysql数据库,对应的就是mysql.xml 文件。 其中下划线标准的部分是我们进行配置的内容,分别是数据url以及数据库用 户名和密码。 3) 修改Build.xml 修改MiddleGen 根目录下的build.xml 文件,此文件是Middlegen-Hibernate 的Ant 构建配置。Middlegen-Hibernate将根据build.xml 文件中的具体参数生成数据库表映射 文件。可配置的项目包括: a) 目标数据库配置文件地址 查找关键字 ”!ENTITY”,得到: ]> 默认情况下,MiddleGen 采用的是hsqldb.xml,将其修改为我们所用的数据 库配置文件(mysql.xml): ]> b) Application name 查找: “aireline”是MiddleGen原始配置中默认的 Application Name,将其修改为我们 所希望的名称,如“HibernateSample”: c) 输出目录 查找关键字“name="build.gen-src.dir"”,得到: 修改value="${build.dir}/gen-src"使其指向我们所期望的输出目录, 这里我们修改为: d) 对应代码的Package name 查找关键字“destination”,得到: 可以看到,hibernate 节点package 属性的默认设置实际上是由前面的 Application Name (${name})和“.hibernate”组合而成,根据我们的需要, 将其改为: 这里还有一个属性genXDocletTags,如果设置为true,则生成的代码将包含 xdoclet tag,这为以后在开发过程中借助xdoclet进行映射调整提供了帮助。关 于Hibernate的xdoclet使用,请参见“高级特性”中的相关内容。 注意,如果使用的数据库为SQLServer,需要将build.xml 中如下部分(下划 线部分)删除,否则Middlegen会报出找不到表的错误。 至此为止,MiddleGen 已经配置完毕,在MiddleGen 根目录下运行ant,就将出现 MiddleGen的界面: 可以看到,数据库中的表结构已经导入到MiddleGen 的操作界面中,选定数据库 表视图中的表元素,我们即可调整各个数据库表的属性。 1 Domain Class Name 对应POJO 的类名 2 Key Generator 主键产生器 可选项说明: 1) Assigned ① ② ③ ④ ⑤ ⑥ ⑦ ⑨ ⑧ ⑩ 主键由外部程序负责生成,无需Hibernate参与。 2) hilo 通过hi/lo 算法实现的主键生成机制,需要额外的数据库表保存主 键生成历史状态。 3) seqhilo 与hilo 类似,通过hi/lo 算法实现的主键生成机制,只是主键历史 状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle。 4) increment 主键按数值顺序递增。此方式的实现机制为在当前应用实例中维持 一个变量,以保存着当前的最大值,之后每次需要生成主键的时候 将此值加1作为主键。 这种方式可能产生的问题是:如果当前有多个实例访问同一个数据 库,那么由于各个实例各自维护主键状态,不同实例可能生成同样 的主键,从而造成主键重复异常。因此,如果同一数据库有多个实 例访问,此方式必须避免使用。 5) identity 采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL 中的主键生成机制。 6) sequence 采用数据库提供的sequence 机制生成主键。如Oralce 中的 Sequence。 7) native 由Hibernate根据底层数据库自行判断采用identity、hilo、sequence 其中一种作为主键生成方式。 8) uuid.hex 由Hibernate基于128 位唯一值产生算法生成16 进制数值(编码后 以长度32 的字符串表示)作为主键。 9) uuid.string 与uuid.hex类似,只是生成的主键未进行编码(长度16)。在某些 数据库中可能出现问题(如PostgreSQL)。 10) foreign 使用外部表的字段作为主键。 一般而言,利用uuid.hex 方式生成主键将提供最好的性能和数据库平台适 应性。 另外由于常用的数据库,如Oracle、DB2、SQLServer、MySql 等,都提 供了易用的主键生成机制(Auto-Increase 字段或者Sequence)。我们可以在数 据库提供的主键生成机制上,采用generator-class=native的主键生成方式。 不过值得注意的是,一些数据库提供的主键生成机制在效率上未必最佳, 大量并发insert数据时可能会引起表之间的互锁。 数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状 态(如对于自增型主键而言,此内部表中就维护着当前的最大值和递增量), 之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之 后再把这个新的最大值更新回内部表中,这样,一次Insert操作可能导致数据 库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生 了较大影响。 因此,对于并发Insert要求较高的系统,推荐采用uuid.hex 作为主键生成 机制。 3 如果需要采用定制的主键产生算法,则在此处配置主键生成器,主键生成器必 须实现net.sf.hibernate.id.IdentifierGenerator 接口。 4 Schema Name 数据库Schema Name。 5 Persister 自定义持久类实现类类名。如果系统中还需要Hibernate 之外的持久层实 现机制,如通过存储过程得到目标数据集,甚至从LDAP中获取数据来填 充我们的POJO。 6 Enable proxies 是否使用代理(用于延迟加载[Lazy Loading])。 7 Dynamic Update 如果选定,则生成Update SQL 时不包含未发生变动的字段属性,这样可 以在一定程度上提升SQL执行效能。 8 Mutable 类是否可变,默认为选定状态(可变)。如果不希望应用程序对此类对应 的数据记录进行修改(如对于数据库视图),则可将取消其选定状态,之 后对此类的Delete和Update操作都将失效。 9 Implement the Lifecyle interface 是否实现Lifecyle接口。Lifecyle接口提供了数据固化过程中的控制机制, 通过实现Lifecyle接口,我们可以在数据库操作中加入回调(Call Back) 机制,如在数据库操作之前,之后触发指定操作。 10 Implement the Validatable interface 是否实现Validatable接口。通过实现Validatable接口,我们可以在数据被 固化到数据库表之前对其合法性进行验证。 值得注意的是,通过实现Lifecyle接口,我们同样可以在数据操作之前验 证数据合法性,不同的是,Validatable 接口中定义的validate 方法可能会 被调用多次,因此设计中应避免在Validatable 接口的validate 方法实现中 加入业务逻辑的验证。 以上是针对Class的设置,同样,在MiddleGen中,我们也可以设定字段属性。在 MiddleGen中选定某个字段,界面下方即出现字段设置栏: 在这里我们可以设置字段的属性,其中: 1 Hibernate mapping specialty 映射类型: Key :主键 Property :属性 Version :用于实现optimistic locking,参见“高级特性”章节中关 于optimistic locking的描述 2 Java property name ① ② ③ ④ ⑤ 字段对应的Java 属性名 3 Java Type 字段对应的Java 数据类型 4 Column updateable 生成Update SQL时是否包含本字段。 5 Column insertable 生成Insert SQL时是否包含本字段。 单击窗口顶部的Generate 按钮,MiddleGen 即为我们生成这些数据库表所对应的 Hibernate映射文件。在MiddleGen根目录下的\build\gen-src\net\hibernate\sample目录中, 我们可以看到对应的以.hbm.xml 作为后缀的多个映射文件,每个映射文件都对应了数 据库的一个表。 仅有映射文件还不够,我们还需要根据这些文件生成对应的POJO。 POJO 的生成工作可以通过Hibernate Extension 来完成,Hibernate Extension 的 tools\bin目录下包含三个工具: 1. hbm2java.bat 根据映射文件生成对应的POJO。通过MiddleGen 我们已经得到了映射文件, 下一步就是通过hbm2java.bat工具生成对应的POJO。 2. class2hbm.bat 根据POJO class 生成映射文件,这个工具很少用到,这里也就不再详细介绍。 3. ddl2hbm.bat 由数据库导出库表结构,并生成映射文件以及POJO。这个功能与MiddleGen 的功能重叠,但由于目前还不够成熟(实际上已经被废弃,不再维护),提供 的功能也有限,所以我们还是采用MiddleGen生成映射文件,之后由hbm2java 根据映射文件生成POJO 的方式。 为了使用以上工具,首先我们需要配置一些参数,打开tools\bin\setenv.bat 文件,修改 其中的JDBC_DRIVER和HIBERNATE_HOME环境变量,使其指向我们的实际JDBC Driver 文件和Hibernate所在目录,如 set JDBC_DRIVER=c:\mysql\mysql.jar set HIBERNATE_HOME=c:\hibernate 同时检查一下环境变量CP中的各个项目中是否实际存在,特别是%CORELIB%下的jar 文件,某些版本的发行包中,默认配置中的文件名与实际的文件名有所出入(如 %CORELIB%\commons-logging.jar, 在Hibernate 发行包中,可能实际的文件名是 commons-logging-1.0.3.jar,诸如此类)。 使用hbm2java,根据MiddleGen生成的映射文件生成Java 代码: 打开Command Window,在tools\bin目录下执行: hbm2java c:\sample\org\hibernate\sample\*.xml --output=c:\sample\ 即可生成对应的POJO。生成的POJO 保存在我们指定的输出目录下(c:\sample)。 目前为止,我们已经完成了通过MiddleGen 产生Hibernate 基础代码的工作。配置 MiddleGen 也许并不是一件轻松的事情,对于Eclipse 的用户而言,目前已经出现了好几个 Hibernate 的Plugin,通过这些Plugin 我们可以更加轻松的完成上述工作,具体的使用方式 请参见附录。 Hibernate 配置 前面已经得到了映射文件和POJO,为了使Hibernate 能真正运作起来,我们还需要一 个配置文件。 Hibernate同时支持xml格式的配置文件,以及传统的properties 文件配置方式,不过这 里建议采用xml 型配置文件。xml配置文件提供了更易读的结构和更强的配置能力,可以直 接对映射文件加以配置,而在properties 文件中则无法配置,必须通过代码中的Hard Coding 加载相应的映射文件。下面如果不作特别说明,都指的是基于xml格式文件的配置方式。 配置文件名默认为“hibernate.cfg.xml”(或者hibernate.properties),Hibernate 初始化期 间会自动在CLASSPATH 中寻找这个文件,并读取其中的配置信息,为后期数据库操作做好 准备。 配置文件应部署在CLASSPATH 中,对于Web 应用而言,配置文件应放置在在 \WEB-INF\classes 目录下。 一个典型的hibernate.cfg.xml配置文件如下: <!--?xml version="1.0" encoding="utf-8"?--> <!--—- SessionFactory 配置 --> <!--—- 数据库URL --> jdbc:mysql://localhost/sample <!--—- 数据库JDBC驱动 --> org.gjt.mm.mysql.Driver <!--—- 数据库用户名 --> User <!--—- 数据库用户密码 --> Mypass <!--dialect ,每个数据库都有其对应的Dialet以匹配其平台特性 --> net.sf.hibernate.dialect.MySQLDialect <!--—- 是否将运行期生成的SQL输出到日志以供调试 --> True <!--—- 是否使用数据库外连接 --> True <!--—- 事务管理类型,这里我们使用JDBC Transaction --> net.sf.hibernate.transaction.JDBCTransactionFactory <!--—映射文件配置,注意配置文件名必须包含其相对于根的全路径 --> 一个典型的hibernate.properties配置文件如下: hibernate.dialect net.sf.hibernate.dialect.MySQLDialect hibernate.connection.driver_class org.gjt.mm.mysql.Driver hibernate.connection.driver_class com.mysql.jdbc.Driver hibernate.connection.url jdbc:mysql:///sample hibernate.connection.username user hibernate.connection.password mypass 第一段代码 上面我们已经完成了Hiberante 的基础代码,现在先从一段最简单的代码入手,感受一 下Hibernate所提供的强大功能。 下面这段代码是一个JUnit TestCase,演示了TUser 对象的保存和读取。考虑到读者可 能没有JUnit的使用经验,代码中加入了一些JUnit相关注释。 public class HibernateTest extends TestCase { Session session = null; /** * JUnit中setUp方法在TestCase初始化的时候会自动调用 * 一般用于初始化公用资源 * 此例中,用于初始化Hibernate Session */ protected void setUp(){ try { /** * 采用hibernate.properties配置文件的初始化代码: * Configuration config = new Configuration(); * config.addClass(TUser.class); */ //采用hibernate.cfg.xml配置文件 //请注意初始化Configuration时的差异: // 1.Configuration的初始化方式 // 2.xml文件中已经定义了Mapping文件,因此无需再Hard Coding导入 // POJO文件的定义 Configuration config = new Configuration().configure(); SessionFactory sessionFactory = config.buildSessionFactory(); session = sessionFactory.openSession(); } catch (HibernateException e) { e.printStackTrace(); } } /** * 与setUp方法相对应,JUnit TestCase执行完毕时,会自动调用tearDown方法 * 一般用于资源释放 * 此例中,用于关闭在setUp方法中打开的Hibernate Session */ protected void tearDown(){ try { session.close(); } catch (HibernateException e) { e.printStackTrace(); } } /** * 对象持久化(Insert)测试方法 * * JUnit中,以”test”作为前缀的方法为测试方法,将被JUnit自动添加 * 到测试计划中运行 */ public void testInsert(){ try { TUser user = new TUser(); user.setName("Emma"); session.save(user); session.flush(); } catch (HibernateException e) { e.printStackTrace(); Assert.fail(e.getMessage()); } } /** * 对象读取(Select)测试 * 请保证运行之前数据库中已经存在name=’Erica’的记录 */ public void testSelect(){ String hql= " from TUser where name='Erica'"; try { List userList = session.find(hql); TUser user =(TUser)userList.get(0); Assert.assertEquals(user.getName(),"Erica"); } catch (HibernateException e) { e.printStackTrace(); Assert.fail(e.getMessage()); } } } 主流IDE,如Eclipse、Intellij IDEA 和JBuilder 中都内置了JUnit支持。下面是Eclipse 中运行该代码的结果(在Run菜单中选择Run as -> JUnit Test即可): 现在我们已经成功实现了一个简单的TUser 实例的保存和读取。可以看到,程序中通过 少量代码实现了Java 对象和数据库数据的同步,同时借助Hibernate的有力支持,轻松实现 了对象到关系型数据库的映射。 相对传统的JDBC数据访问模式,这样的实现无疑更符合面向对象的思想,同时也大大 提高了开发效率。 上面的代码中引入了几个Hibernate基础语义: 1. Configuration 2. SessionFactory 3. Session 下面我们就这几个关键概念进行探讨。 Hibernate基础语义 Configuration 正如其名,Configuration 类负责管理Hibernate 的配置信息。Hibernate 运行时需要 获取一些底层实现的基本信息,其中几个关键属性包括: 1. 数据库URL 2. 数据库用户 3. 数据库用户密码 4. 数据库JDBC驱动类 5. 数据库dialect,用于对特定数据库提供支持,其中包含了针对特定数据库特性 的实现,如Hibernate数据类型到特定数据库数据类型的映射等。 使用Hibernate 必须首先提供这些基础信息以完成初始化工作,为后继操作做好准 备。这些属性在hibernate配置文件(hibernate.cfg.xml 或hibernate.properties)中加以设 定(参见前面“Hibernate配置”中的示例配置文件内容)。 当我们调用: Configuration config = new Configuration().configure(); 时,Hibernate会自动在当前的CLASSPATH 中搜寻hibernate.cfg.xml 文件并将其读 取到内存中作为后继操作的基础配置。Configuration 类一般只有在获取SessionFactory 时需要涉及,当获取SessionFactory 之后,由于配置信息已经由Hibernate 维护并绑定 在返回的SessionFactory之上,因此一般情况下无需再对其进行操作。 我们也可以指定配置文件名,如果不希望使用默认的hibernate.cfg.xml 文件作为配 置文件的话: File file = new File("c:\\sample\\myhibernate.xml"); Configuration config = new Configuration().configure(file); SessionFactory SessionFactory 负责创建Session 实例。我们可以通过Configuation 实例构建 SessionFactory: Configuration config = new Configuration().configure(); SessionFactory sessionFactory = config.buildSessionFactory(); Configuration实例config会根据当前的配置信息,构造SessionFactory实例并返回。 SessionFactory 一旦构造完毕,即被赋予特定的配置信息。也就是说,之后config 的任 何变更将不会影响到已经创建的SessionFactory 实例(sessionFactory)。如果需要 使用基于改动后的config 实例的SessionFactory,需要从config 重新构建一个 SessionFactory实例。 Session Session是持久层操作的基础,相当于JDBC中的Connection。 Session实例通过SessionFactory实例构建: Configuration config = new Configuration().configure(); SessionFactory sessionFactory = config.buildSessionFactory(); Session session = sessionFactory.openSession(); 之后我们就可以调用Session所提供的save、find、flush等方法完成持久层操作: Find: String hql= " from TUser where name='Erica'"; List userList = session.find(hql); Save: TUser user = new TUser(); user.setName("Emma"); session.save(user); session.flush(); 最后调用Session.flush方法强制数据库同步,这里即强制Hibernate将user实 例立即同步到数据库中。如果在事务中则不需要flush方法,在事务提交的时候, hibernate自动会执行flush方法,另外当Session关闭时,也会自动执行flush方法。 Hibernate高级特性 XDoclet 与Hibernate 映射 在POJO 中融合XDoclet 的映射文件自动生成机制,提供了除手动编码和由数据库导出 基础代码的第三种选择。 本章将结合XDoclet对Hibernate中的数据映射进行介绍。 实际开发中,往往首先使用MiddleGen 和hbm2java 工具生成带有XDoclet tag的POJO (MiddleGen build.xml中的genXDocletTags选项决定了是否在映射文件中生成XDoclet Tag, 详见Hibernate Quick Start章节中关于MiddleGen的说明)。之后通过修改POJO中的XDoclet tag进行映射关系调整。 XDoclet已经广泛运用在EJB开发中,在其最新版本里,包含了一个为Hibernate提供支 持的子类库Hibernate Doclet,其中包含了生成Hibernate映射文件所需的ant构建支持以及 java doc tag支持。 XDoclet实现基本原理是,通过在Java代码加入特定的JavaDoc tag,从而为其添加特定 的附加语义,之后通过XDoclet工具对代码中JavaDoc Tag进行分析,自动生成与代码对应 的配置文件,XDoclet。 在Hibernate-Doclet中,通过引入Hibernate相关的JavaDoc tag,我们就可以由代码生成 对应的Hibernate映射文件。 下面是一个代码片断,演示了Hibernate-Doclet的使用方式: /** * @hibernate.class * table="TUser" */ public class TUser implements Serializable { …… /** * @hibernate.property * column="name" * length="50" * not-null="true" * * @return String */ public String getName() { return this.name; } …… } 以上是使用Hibernate-Doclet 描述POJO(TUser)及其对应表(TUser)之间映射关系 的一个例子。 其中用到了两个hibernate doclet tag,@hibernate.class和@hibernate.property。 这两个tag分别描述了POJO所对应的数据库表信息,以及其字段对应的库表字段信息。 之后Hibernate Doclet就会根据这些信息生成映射文件: < hibernate-mapping> 这样我们只需要维护Java 代码,而无需再手动编写具体的映射文件即可完成Hibernate 基础代码。 熟记Hibernate-Doclet 众多的Tag,显然不是件轻松的事情,好在目前的主流IDE 都提 供了Live Template支持。我们只需进行一些配置工作,就可以实现Hibernate-Doclet Tag 的自动补全功能,从而避免了手工编写过程中可能出现的问题。 附录中提供了主流IDE,包括JBuilder,Intellij IDEA,Eclipse的Hibernate-Doclet集成 指南。 下面我们就Hibernate Doclet 中常用的Tag 进行探讨,关于Tag 的详细参考,请参见 XDoclet 的官方指南(http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html)以及 Hibernate Reference(http://www.hibernate.org)。 常用Hibernate-Doclet Tag介绍: 1. Class 层面: 1) @hibernate.class 描述POJO 与数据库表之间的映射关系,并指定相关的运行参数。 参数 描述 类型 必须 table 类对应的表名 默认值:当前类名 Text N dynamic-update 生成Update SQL时,仅包含发生变动 的字段 默认值: false Bool N dynamic-insert 生成Insert SQL时,仅包含非空(null) 字段 默认值:false Bool N Proxy 代理类 默认值:空 Text N discriminator-value 子类辨别标识,用于多态支持。 Text N where 数据甄选条件,如果只需要处理库表中某 些特定数据的时候,可通过此选项设定结 果集限定条件。 如用户表中保存了全国所有用户的数据, 而我们的系统只是面向上海用户,则可指 定where=”location=’Shanghai’" Text N 典型场景: /** * @hibernate.class * table="TUser" (1) * dynamic-update="true" (2) * dynamic-insert="true" (3) * proxy=”” (4) * discriminator-value=”1” (5) */ public class TUser implements Serializable { …… } 本例中: 1 table参数指定了当前类(TUser)对应数据库表“TUser”。 2 dynamic-update 参数设定为生成Update SQL 时候,只包括当前发生变化的 字段(提高DB Update性能)。 3 Dynamic-insert 参数设定为生成Insert SQL 时候,只包括当前非空字段。 (提高DB Insert性能) 4 Proxy 参数为空,表明当前类不使用代理(Proxy)。代理类的作用是为Lazy Loading提供支持,请参见下面关于Lazy Loading的有关内容。 5 discriminator-value参数设为”1”。 discriminator-value 参数的目的是对多态提供支持。请参见下面关于 @hibernate.discriminator的说明。 2) @hibernate.discriminator @hibernate.discriminator(识别器) 用于提供多态支持。 参数 描述 类型 必须 column 用于区分各子类的字段名称。 默认值:当前类名 text Y type 对应的Hibernate类型 Bool N length 字段长度 Bool N 如: TUser类对应数据库表TUser,并且User类有两个派生类SysAdmin、 SysOperator。 在TUser表中, 根据user_type字段区分用户类型。 为了让Hibernate根据user_type能自动识别对应的Class类型(如 user_type==1 则自动映射到SysAdmin类,user_type==2 则自动映射到SysOperator类),我们需要 在映射文件中进行配置,而在Hibernate-Doclet中,对应的就是 @hibernate.discriminator 标识和 @hibernate.class 以及 @hibernate.subclass 的 discriminator-value属性。 典型场景: /** * * @hibernate.class * table="TUser" * dynamic-update="true" * dynamic-insert="true" * * @hibernate.discriminator column="user_type" type="integer" */ public class TUser implements Serializable { …… } 根类TUser 中,通过@hibernate.discriminator 指定了以"user_type"字段 作为识别字段。 /** * @hibernate.subclass * discriminator-value="1" */ public class SysAdmin extends TUser { …… } /** * @hibernate.subclass * discriminator-value="2" */ public class SysOperator extends TUser { …… } SysAdmin 和SysOperator 均继承自TUser,其discriminator-value 分别设置 为"1"和"2",运行期Hibernate 在读取t_user 表数据时,会根据其user_type 字段进行 判断,如果是1 的话则映射到SysAdmin类,如果是2 映射到SysOperator 类。 上例中,描述SysAdmin 和SysOperator 时,我们引入了一个Tag: @hibernate.subclass,顾名思义,@hibernate.subclass与@hibernate.class 不同之处就在于,@hibernate.subclass 描述的是一个子类,实际上,这两个Tag 除去名称不同外,并没有什么区别。 2. Method层面: 1) @hibernate.id 描述POJO 中关键字段与数据库表主键之间的映射关系。 参数 描述 类型 必须 column 主键字段名 默认值:当前类名 Text N type 字段类型。 Hibernate总是使用对象型数据类型作 为字段类型,如int对应Integer,因此 这里将id设为基本类型[如int]以避免对 象创建的开销的思路是没有实际意义的, 即使这里设置为基本类型,Hibernate内 部还是会使用对象型数据对其进行处理, 只是返回数据的时候再转换为基本类型 而已。 Text N length 字段长度 Text N unsaved-value 用于对象是否已经保存的判定值。 详见“数据访问”章节的相关讨论。 Text N generator-class 主键产生方式(详见Hibernate Quick Start中关于MiddleGen的相关说明) 取值可为下列值中的任意一个: assigned hilo seqhilo increment identity sequence native uuid.hex uuid.string foreign Text Y 2) @hibernate.property 描述POJO 中属性与数据库表字段之间的映射关系。 参数 描述 类型 必须 column 数据库表字段名 默认值:当前类名 Text N type 字段类型 Text N length 字段长度 Text N not-null 字段是否允许为空 Bool N unique 字段是否唯一(是否允许重复值) Bool N insert Insert 操作时是否包含本字段数据 默认:true Bool N update Update操作时是否包含本字段数据 默认:true Bool N 典型场景: /** * @hibernate.property * column="name" * length="50" * not-null="true" * * @return String */ public String getName() { return this.name; } 注意:在编写代码的时候请,对将POJO的getter/setter方法设定为public,如果 设定为private,Hibernate将无法对属性的存取进行优化,只能转而采用传统的反射机制 进行操作,这将导致大量的性能开销(特别是在1.4之前的Sun JDK版本以及IBM JDK中, 反射所带来的系统开销相当可观)。 包含XDoclet Tag的代码必须由xdoclet程序进行处理以生成对应的映射文件, xdoclet的处理模块可通过ant进行加载,下面是一个简单的hibernate xdoclet的ant 构建脚本(注意实际使用时需要根据实际情况对路径和CLASSPATH设定进行调整): <!--?xml version="1.0"?--> 除了上面我们介绍的Hibernate Doclet Tag,其他还有: Class层面; @hibernate.cache @hibernate.jcs-cache @hibernate.joined-subclass @hibernate.joined-subclass-key @hibernate.query Method层面 @hibernate.array @hibernate.bag @hibernate.collection-cache @hibernate.collection-composite-element @hibernate.collection-element @hibernate.collection-index @hibernate.collection-jcs-cache @hibernate.collection-key @hibernate.collection-key-column @hibernate.collection-many-to-many @hibernate.collection-one-to-many @hibernate.column @hibernate.component @hibernate.generator-param @hibernate.index-many-to-many @hibernate.list @hibernate.many-to-one @hibernate.map @hibernate.one-to-one @hibernate.primitive-array @hibernate.set @hibernate.timestamp @hibernate.version 具体的Tag描述请参见XDoclet官方网站提供的Tag说明1。下面的Hibernate高级特性介 绍中,我们也将涉及到这些Tag的实际使用。 1 http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html 数据检索 数据查询与检索是Hibernate中的一个亮点。相对其他ORM实现而言,Hibernate 提供了灵活多样的查询机制。其中包括: 1. Criteria Query 2. Hibernate Query Language (HQL) 3. SQL Criteria Query Criteria Query通过面向对象化的设计,将数据查询条件封装为一个对象。简单来 讲,Criteria Query可以看作是传统SQL的对象化表示,如: Criteria criteria = session.createCriteria(TUser.class); criteria.add(Expression.eq("name","Erica")); criteria.add(Expression.eq("sex",new Integer(1))); 这里的criteria 实例实际上是SQL “Select * from t_user where name=’Erica’ and sex=1”的封装(我们可以打开Hibernate 的show_sql 选项, 以观察Hibernate在运行期生成的SQL语句)。 Hibernate 在运行期会根据Criteria 中指定的查询条件(也就是上面代码中通过 criteria.add方法添加的查询表达式)生成相应的SQL语句。 这种方式的特点是比较符合Java 程序员的编码习惯,并且具备清晰的可读性。正因 为此,不少ORM实现中都提供了类似的实现机制(如Apache OJB)。 对于Hibernate的初学者,特别是对SQL了解有限的程序员而言,Criteria Query 无疑是上手的极佳途径,相对HQL,Criteria Query提供了更易于理解的查询手段,借 助IDE的Coding Assist机制,Criteria的使用几乎不用太多的学习。 Criteria 查询表达式 Criteria 本身只是一个查询容器,具体的查询条件需要通过Criteria.add 方法添加到Criteria实例中。 如前例所示,Expression 对象具体描述了查询条件。针对SQL 语法, Expression提供了对应的查询限定机制,包括: 方法 描述 Expression.eq 对应SQL“field = value”表达式。 如Expression.eq("name","Erica") Expression.allEq 参数为一个Map对象,其中包含了多个属性-值对 应关系。相当于多个Expression.eq关系的叠加。 Expression.gt 对应SQL中的 “field > value ” 表达式 Expression.ge 对应SQL中的 “field >= value” 表达式 Expression.lt 对应SQL中的 “field < value” 表达式 Expression.le 对应SQL中的 “field <= value” 表达式 Expression.between 对应SQL中的 “between” 表达式 如下面的表达式表示年龄(age)位于13到50区 间内。 Expression.between("age",new Integer(13),new Integer(50)); Expression.like 对应SQL中的 “field like value” 表达式 Expression.in 对应SQL中的 ”field in …” 表达式 Expression.eqProperty 用于比较两个属性之间的值,对应SQL中的“field = field”。 如: Expression.eqProperty( "TUser.groupID", "TGroup.id" ); Expression.gtProperty 用于比较两个属性之间的值,对应SQL中的“field > field”。 Expression.geProperty 用于比较两个属性之间的值,对应SQL中的“field >= field”。 Expression.ltProperty 用于比较两个属性之间的值,对应SQL中的“field < field”。 Expression.leProperty 用于比较两个属性之间的值,对应SQL中的“field <= field”。 Expression.and and关系组合。 如: Expression.and( Expression.eq("name","Erica"), Expression.eq( "sex", new Integer(1) ) ); Expression.or or关系组合。 如: Expression.or( Expression.eq("name","Erica"), Expression.eq("name","Emma") ); Expression.sql 作为补充,本方法提供了原生SQL语法的支持。我 们可以通过这个方法直接通过SQL语句限定查询 条件。 下面的代码返回所有名称以“Erica”起始的记录: Expression.sql( “lower({alias}.name) like lower(?)”, "Erica%", Hibernate.STRING ); 其中的“{alias}”将由Hibernate在运行期使 用当前关联的POJO别名替换。 注意Expression 各方法中的属性名参数(如Express.eq中的第一个参数),这里 所谓属性名是POJO中对应实际库表字段的属性名(大小写敏感),而非库表中的实 际字段名称。 Criteria 高级特性 限定返回的记录范围 通过criteria. setFirstResult/setMaxResults 方法可以限制一次查询返回 的记录范围: Criteria criteria = session.createCriteria(TUser.class); //限定查询返回检索结果中,从第一百条结果开始的20条记录 criteria.setFirstResult(100); criteria.setMaxResults(20); 对查询结果进行排序 //查询所有groupId=2的记录 //并分别按照姓名(顺序)和groupId(逆序)排序 Criteria criteria = session.createCriteria(TUser.class); criteria.add(Expression.eq("groupId",new Integer(2))); criteria.addOrder(Order.asc("name")); criteria.addOrder(Order.desc("groupId")); Criteria作为一种对象化的查询封装模式,不过由于Hibernate在实现过程中将精力 更加集中在HQL查询语言上,因此Criteria的功能实现还没做到尽善尽美(这点上,OJB 的Criteria 实现倒是值得借鉴),因此,在实际开发中,建议还是采用Hibernate 官 方推荐的查询封装模式:HQL。 Hibernate Query Language (HQL) Criteria提供了更加符合面向对象编程模式的查询封装模式。不过,HQL(Hibernate Query Language)提供了更加强大的功能,在官方开发手册中,也将HQL作为推荐的查询 模式。 相对Criteria,HQL提供了更接近传统SQL语句的查询语法,也提供了更全面的特性。 最简单的一个例子: String hql = "from org.hibernate.sample.TUser"; Query query = session.createQuery(hql); List userList = query.list(); 上面的代码将取出TUser的所有对应记录。 如果我们需要取出名为“Erica”的用户的记录,类似SQL,我们可以通过SQL 语句加 以限定: String hql = "from org.hibernate.sample.TUser as user where user.name='Erica'"; Query query = session.createQuery(hql); List userList = query.list(); 其中我们新引入了两个子句“as”和“where”,as子句为类名创建了一个别名,而where 子句指定了限定条件。 HQL 子句本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。 关于HQL,Hibernate 官方开发手册中已经提供了极其详尽的说明和示例,详见 Hibernate官方开发手册(Chapter 11)。 数据关联 一对一关联 配置: Hibernate中的一对一关联由“one-to-one”节点定义。 在我们的权限管理系统示例中,每个用户都从属于一个用户组。如用户“Erica” 从属于“System Admin”组,从用户的角度出发,这就是一个典型的(单向)一对 一关系。 每个用户对应一个组,这在我们的系统中反映为TUser 到 TGroup 的 one-to-one 关系。其中TUser 是主控方,TGroup是被动方。 one-to-one关系定义比较简单,只需在主控方加以定义。这里,我们的目标是 由TUser 对象获取其对应的TGroup 对象。因此TUser 对象是主控方,为了实现一 对一关系,我们在TUser 对象的映射文件TUser.hbm.xml 中加入one-to-one节 点,对TGroup对象进行一对一关联: …… …… 如果采用XDoclet,则对应的Tag如下: /** * @hibernate.class * table="t_user" * dynamic-update="true" * dynamic-insert="true" * */ public class TUser implements Serializable { …… private TGroup group; /** * @hibernate.one-to-one * name="group" * cascade="none" * class="org.hibernate.sample.TGroup" * outer-join="auto" * @return */ public TGroup getGroup() { return group; } …… } one-to-one 节点有以下属性: 属性 描述 类型 必须 name 映射属性 Text N class 目标映射类。 注意要设为包含Package name的全路 径名称。 Text N cascade 操作级联(cascade)关系。 可选值: all : 所有情况下均进行级联操作。 none:所有情况下均不进行级联操作。 save-update:在执行save-update时 进行级联操作。 delete:在执行delete时进行级联操作。 级联(cascade)在Hibernate映射关 系中是个非常重要的概念。它指的是当主 控方执行操作时,关联对象(被动方)是 否同步执行同一操作。如对主控对象调用 save-update或delete方法时,是否同 时对关联对象(被动方)进行 Text N save-update或delete。 这里,当用户(TUser)被更新或者删除 时,其所关联的组(TGroup)不应被修 改或者删除,因此,这里的级联关系设置 为none。 constrained 约束 表明主控表的主键上是否存在一个外键 (foreign key)对其进行约束。这个选 项关系到save、delete等方法的级联操 作顺序。 Bool N outer-join 是否使用外联接。 true:总是使用outer-join false:不使用outer-join auto(默认) :如果关联对象没有采用 Proxy机制,则使用outer-join. Text N property-ref 关联类中用于与主控类相关联的属性名 称。 默认为关联类的主键属性名。 这里我们通过主键达成一对一的关联,所 以采用默认值即可。如果一对一的关联并 非建立在主键之间,则可通过此参数指定 关联属性。 Text N access 属性值的读取方式。 可选项: field property(默认) ClassName Text N 一对多关联 一对多关系在系统实现中也很常见。典型的例子就是父亲与孩子的关系。 而在我 们现在的这个示例中,每个用户(TUser)都关联到多个地址(TAddress),如一个 用户可能拥有办公室地址、家庭地址等多个地址属性。这样,在系统中,就反应为一 个“一对多”关联。 一对多关系分为单向一对多关系和双向一对多关系。 单向一对多关系只需在“一”方进行配置,双向一对多关系需要在关联双方均加 以配置。 Ø 单向一对多关系 配置: 对于主控方(TUser): TUser.hbm.xml: …… …… 对应的XDoclet Tag 如下: /** * @hibernate.collection-one-to-many * class="org.hibernate.sample.TAddress" * * @hibernate.collection-key column="user_id" * * @hibernate.set * name="addresses" * table="t_address" * inverse="false" * cascade="all" * lazy="false" * sort=”unsorted” * order-by="zipcode asc" * */ public Set getAddresses() { return addresses; } 被动方(Taddress)的记录由Hibernate 负责读取,之后存放在主控方指定的 Collection类型属性中。 对于one-to-many 关联关系, 我们可以采用java.util.Set ( 或者 net.sf.hibernate.collection.Bag)类型的Collection,表现在XML 映射文件 中也就是…(或…)节点。关于Hibernate的Collection 实现,请参见Hibernate Reference. one-to-many 节点有以下属性: 属性 描述 类型 必须 name 映射属性 Text Y table 目标关联数据库表。 Text Y lazy 是否采用延迟加载。 关于延迟加载,请参见后面相关章节。 Text N inverse 用于标识双向关联中的被动方一端。 inverse=false的一方(主控方)负责 维护关联关系。 默认值: false Bool N cascade 操作级联(cascade)关系。 可选值: all : 所有情况下均进行级联操作。 none:所有情况下均不进行级联操作。 save-update:在执行save-update时 进行级联操作。 delete:在执行delete时进行级联操作。 Text N sort 排序类型。 Text N 可选值: unsorted :不排序(默认) natural :自然顺序(避免与order-by 搭配使用) comparatorClass :指以某个实现了 java.util.Comparator接口的类作为排 序算法。 order-by 指定排序字段及其排序方式。 (JDK1.4以上版本有效)。 对应SQL中的order by子句。 避免与sort 的 “natural”模式同时使 用。 Text N where 数据甄选条件,如果只需要处理库表中某 些特定数据的时候,可通过此选项设定结 果集限定条件。 Text N outer-join 是否使用外联接。 true:总是使用outer-join false:不使用outer-join auto(默认) :如果关联对象没有采用 Proxy机制,则使用outer-join. Text N batch-size 采用延迟加载特性时(Lazy Loading) 一次读入的数据数量。 此处未采用延迟加载机制,因此此属性忽 略。 Int N access 属性值的读取方式。 可选项: field property(默认) ClassName Text N 通过单向一对多关系进行关联相对简单,但是存在一个问题。由于是单向关联, 为了保持关联关系,我们只能通过主控方对被动方进行级联更新。且如果被关联方的 关联字段为“NOT NULL”,当Hibernate创建或者更新关联关系时,还可能出现约 束违例。 例如我们想为一个已有的用户“Erica”添加一个地址对象: Transaction tx = session.beginTransaction(); TAddress addr = new TAddress(); addr.setTel("1123"); addr.setZipcode("233123"); addr.setAddress("Hongkong"); user.getAddresses().add(addr); session.save(user);//通过主控对象级联更新 tx.commit(); 为了完成这个操作,Hibernate会分两步(两条SQL)来完成新增t_address 记录的操作: 1. save(user)时: insert into t_address (user_id, address, zipcode, tel) values (null, "Hongkong", "233123", "1123") 2. tx.commit()时 update t_address set user_id=”1”, address="Hongkong", zipcode="233123", tel="1123" where id=2 第一条SQL用于插入新的地址记录。 第二条SQL用于更新t_address,将user_id设置为其关联的user对象的id值。 问题就出在这里,数据库中,我们的t_address.user_id字段为“NOT NULL” 型,当Hibernate执行第一条语句创建t_address记录时,试图将user_id字段的 值设为null,于是引发了一个约束违例异常: net.sf.hibernate.PropertyValueException: not-null property references a null or transient value: org.hibernate.sample.TAddress.userId 因为关联方向是单向,关联关系由TUser对象维持,而被关联的addr对象本身并 不知道自己与哪个TUser对象相关联,也就是说,addr对象本身并不知道user_id应 该设为什么数值。 因此,在保存addr时,只能先在关联字段插入一个空值。之后,再由TUser对象 将自身的id值赋予关联字段addr.user_id,这个赋值操作导致addr对象属性发生变 动,在事务提交时,hibernate会发现这一改变,并通过update sql将变动后的数 据保存到数据库。 第一个步骤中,企图向数据库的非空字段插入空值,因此导致了约束违例。 既然TUser对象是主控方,为什么就不能自动先设置好下面的TAddress对象的 关俩字段值再一次做Insert操作呢?莫名其妙?Ha,don’t ask me ,go to ask Hibernate TeamJ。 我们可以在设计的时候通过一些手段进行调整,以避免这样的约束违例,如将关 联字段设为允许NULL值、直接采用数值型字段作为关联(有的时候这样的调整并不可 行,很多情况下我们必须针对现有数据库结构进行开发),或者手动为关联字段属性 赋一个任意非空值(即使在这里通过手工设置了正确的user_id也没有意义, hibernate还是会自动再调用一条Update语句进行更新)。 甚至我们可以将被动方的关联字段从其映射文件中剔除(如将user_id字段的映 射从TAddress.hbm.xml中剔除)。这样Hibernate在生成第一条insert语句的时 候就不会包含这个字段(数据库会使用字段默认值填充),如:之后update语句会根 据主控方的one-to-many映射配置中的关联字段去更新被动方关联字段的内容。在我 们这里的例子中,如果将user_id字段从TAddress.hbm.xml文件中剔除, Hibernate在保存数据时会生成下面几条SQL: 1. insert into t_address (address, zipcode, tel) values ('Hongkong', '233123', '1123') 2. update t_address set user_id=1 where id=7 生成第一条insert语句时,没有包含user_id字段,数据库会使用该字段的默 认值(如果有的话)进行填充。因此不会引发约束违例。之后,根据第一条语句返回 的记录id,再通过update语句对user_id字段进行更新。 但是,纵使采用这些权益之计,由于Hibernate实现机制中,采用了两条SQL进 行一次数据插入操作,相对单条insert,几乎是两倍的性能开销,效率较低,因此, 对于性能敏感的系统而言,这样的解决方案所带来的开销可能难以承受。 针对上面的情况,我们想到,如果addr对象知道如何获取user_id字段的内容, 那么执行insert语句的时候直接将数据植入即可。这样不但绕开了约束违例的可能, 而且还节省了一条Update语句的开销,大幅度提高了性能。 双向一对多关系的出现则解决了这个问题。它除了避免约束违例和提高性能的好 处之外,还带来另外一个优点,由于建立了双向关联,我们可以在关联双方中任意一 方,访问关联的另一方(如可以通过TAddress对象直接访问其关联的TUser对象), 这提供了更丰富灵活的控制手段。 Ø 双向一对多关系 双向一对多关系,实际上是“单向一对多关系”与“多对一关系”的组合。也就 是说我们必须在主控方配置单向一对多关系的基础上,在被控方配置多对一关系与其 对应。 配置: 上面我们已经大致完成了单向方一对多关系的配置,我们只需在此基础上稍做修 改,并对(t_address)的相关属性进行配置即可: TUser.hbm.xml: …… ① 这里与前面不同,inverse被设为“true”,这意味着TUser不再作为主控方, 而是将关联关系的维护工作交给关联对象org.hibernate.sample.TAddress 来 完成。这样TAddress对象在持久化过程中,就可以主动获取其关联的TUser对象的id, 并将其作为自己的user_id,之后执行一次insert操作即可完成全部工作。 在one-to-many 关系中,将many 一方设为主动方(inverse=false)将有助性能 的改善。(现实中也一样,如果要让记住全国人民的名字,估计花个几十年也 不可能,但要让全国人民知道,可就不需要那么多时间了。J) 对应的 xdoclet tag 如下: public class TUser implements Serializable { …… private Set addresses = new HashSet(); …… /** * @hibernate.collection-one-to-many * class="org.hibernate.sample.TAddress" * * @hibernate.collection-key column="user_id" * * @hibernate.set * name="addresses" * table="t_address" * inverse="true" * lazy="false" * cascade=”all” * sort="unsorted" * order-by="zipcode asc" */ public Set getAddresses() { return addresses; } …… } TAddress.hbm.xml: …… ① 在TAddress 对象中新增一个TUser field “user”,并为其添加对应的 getter/setter 方法。同时删除原有的user_id 属性及其映射配置,否则运行期会报 字段重复映射错误:“Repeated column in mapping”。 对应Xdoclet tag: public class TAddress implements Serializable { …… private TUser user; …… /** * @hibernate.many-to-one * name="user" * column="user_id" * not-null="true" * */ public TUser getUser() { return this.user; } …… } 再看上面那段代码片断: Criteria criteria = session.createCriteria(TUser.class); criteria.add(Expression.eq("name","Erica")); List userList = criteria.list(); TUser user =(TUser)userList.get(0); Transaction tx = session.beginTransaction(); TAddress addr = new TAddress(); addr.setTel("1123"); addr.setZipcode("233123"); addr.setAddress("Hongkong"); user.getAddresses().add(addr); session.save(user);//通过主控对象级联更新 tx.commit(); 尝试运行这段代码,结果凄凉的很,还是约束违例。 为什么会这样,我们已经配置了TAddress的many-to-one关系,这么看来似 乎没什么效果…… 不过,别忘了上面提到的inverse 属性,这里我们把TUser 的inverse 设为 “true”,即指定由对方维护关联关系,在这里也就是由TAddress维护关联关系。 TUser既然不再维护关联关系,那么TAddress的user_id属性它也自然不会关心, 必须由TAddress自己去维护user_id: …… TAddress addr = new TAddress(); addr.setTel("1123"); addr.setZipcode("233123"); addr.setAddress("Hongkong"); addr.setUser(user);//设置关联的TUser对象 user.getAddresses().add(addr); session.save(user);//级联更新 …… 观察Hibernate执行过程中调用的SQL语句: insert into t_address (user_id, address, zipcode, tel) values (1, 'Hongkong', '233123', '1123') 正如我们所期望的,保存工作通过单条Insert语句的执行来完成。 many-to-one 节点有以下属性: 属性 描述 类型 必须 name 映射属性 Text Y column 关联字段。 Text N class 类名 默认为映射属性所属类型 Text N cascade 操作级联(cascade)关系。 可选值: all : 所有情况下均进行级联操作。 none:所有情况下均不进行级联操作。 save-update:在执行save-update时 进行级联操作。 delete:在执行delete时进行级联操作。 Text N update 是否对关联字段进行Update操作 Bool N 默认:true insert 是否对关联字段进行Insert操作 默认:true Bool N outer-join 是否使用外联接。 true:总是使用outer-join false:不使用outer-join auto(默认) :如果关联对象没有采用 Proxy机制,则使用outer-join. Text N property-ref 用于与主控类相关联的属性的名称。 默认为关联类的主键属性名。 这里我们通过主键进行关联,所以采用默 认值即可。如果关联并非建立在主键之 间,则可通过此参数指定关联属性。 Text N access 属性值的读取方式。 可选项: field property(默认) ClassName Text N 级联与关联关系的差别? 多对多关联 Hibernate关联关系中相对比较特殊的就是多对多关联,多对多关联与一对一关 联和一对多关联不同,多对多关联需要另外一张映射表用于保存多对多映射信息。 由于多对多关联的性能不佳(由于引入了中间表,一次读取操作需要反复数次查 询),因此在设计中应该避免大量使用。同时,在对多对关系中,应根据情况,采取 延迟加载(Lazy Loading 参见后续章节)机制来避免无谓的性能开销。 在一个权限管理系统中,一个常见的多对多的映射关系就是Group 与Role,以 及Role与Privilege之间的映射。 Ø Group代表“组”(如“业务主管”); Ø Role代表“角色”(如“出纳”、“财务”); Ø Privilege 代表某个特定资源的访问权限(如“修改财务报表”,“查询 财务报表”)。 这里我们以Group和Role之间的映射为例: Ø 一个Group中包含了多个Role,如某个“业务主管”拥有“出纳”和“财 务”的双重角色。 Ø 而一个Role也可以属于不同的Group。 配置: 在我们的实例中,TRole 和TPrivilege 对应数据库中的t_role、 t_privilege表。 TGroup.hbm.xml中关于多对多关联的配置片断: …… ① 这里为t_group 和t_role之间的映射表。 ② 一般情况下,cascade应该设置为“save-update”,对于多对多逻辑 而言,很少出现删除一方需要级联删除所有关联数据的情况,如删除一 个Group,一般不会删除其中包含的Role(这些Role 可能还被其他的 Group所引用)。反之删除Role一般也不会删除其所关联的所有Group。 ③ 映射表中对于t_group表记录的标识字段。 ④ 映射表中对于t_role表记录的标识字段。 对应的xdoclet tag如下: public class TGroup implements Serializable { …… private Set roles = new HashSet(); /** * @hibernate.set * name="roles" * table="t_group_role" * lazy="false" * inverse="false" * cascade="save-update" * sort=”unsorted” * * @hibernate.collection-key * column="group_id" * * @hibernate.collection-many-to-many * class="org.hibernate.sample.TRole" * column="role_id" * */ public Set getRoles() { return roles; } …… } TRole.hbm.xml中关于多对多关联的配置片断: …… 对应的xdoclet如下: public class TRole implements Serializable { private Set groups = new HashSet(); …… /** * * @hibernate.set * name="groups" * table="t_group_role" * cascade="save-update" * inverse="true" * lazy="false" * * @hibernate.collection-key * column="role_id" * * @hibernate.collection-many-to-many * class="org.hibernate.sample.TGroup" * column="group_id" * * */ public Set getGroups() { return groups; } } many-to-many节点中各个属性描述: 属性 描述 类型 必须 column 中间映射表中,关联目标表的关联字段。 Text Y class 类名 关联目标类。 Text Y outer-join 是否使用外联接。 true:总是使用outer-join false:不使用outer-join auto(默认) :如果关联对象没有采用 Proxy机制,则使用outer-join. Text N 使用: 多对多关系中,由于关联关系是两张表相互引用,因此在保存关联状态时必须对 双方同时保存。 public void testPersist(){ TRole role1 = new TRole(); role1.setName("Role1"); TRole role2 = new TRole(); role2.setName("Role2"); TRole role3 = new TRole(); role3.setName("Role3"); TGroup group1 = new TGroup(); group1.setName("group1"); TGroup group2 = new TGroup(); group2.setName("group2"); TGroup group3 = new TGroup(); group3.setName("group3"); group1.getRoles().add(role1); group1.getRoles().add(role2); group2.getRoles().add(role2); group2.getRoles().add(role3); group3.getRoles().add(role1); group3.getRoles().add(role3); role1.getGroups().add(group1); role1.getGroups().add(group3); role2.getGroups().add(group1); role2.getGroups().add(group2); role3.getGroups().add(group2); role3.getGroups().add(group3); try { Transaction tx = session.beginTransaction(); //多对多关系必须同时对关联双方进行保存 session.save(role1); session.save(role2); session.save(role3); session.save(group1); session.save(group2); session.save(group3); tx.commit(); } catch (Exception e) { e.printStackTrace(); Assert.fail(e.getMessage()); } } 上面的代码创建3个TGroup对象和3个TRole对象,并形成了多对多关系。 数据访问 PO和VO PO即 Persistence Object VO即 Value Object PO和VO是Hibernate中两个比较关键的概念。 首先,何谓VO,很简单,VO就是一个简单的值对象。 如: TUser user = new TUser(); user.setName("Emma"); 这里的user就是一个VO。VO只是简单携带了对象的一些属性信息。 何谓PO? 即纳入Hibernate管理框架中的VO。看下面两个例子: TUser user = new TUser(); TUser anotherUser = new TUser(); user.setName("Emma"); anotherUser.setName("Kevin"); //此时user和anotherUser都是VO Transaction tx = session.beginTransaction(); session.save(user); //此时的user已经经过Hibernate的处理,成为一个PO //而anotherUser仍然是个VO tx.commit(); //事务提交之后,库表中已经插入一条用户”Emma”的记录 //对于anotherUser则无任何操作 Transaction tx = session.beginTransaction(); user.setName("Emma_1"); //PO anotherUser.setName("Kevin_1");//VO tx.commit(); //事务提交之后,PO的状态被固化到数据库中 //也就是说数据库中“Emma”的用户记录已经被更新为“Emma_1” //此时anotherUser仍然是个普通Java对象,它的属性更改不会 //对数据库产生任何影响 另外,通过Hibernate返回的对象也是PO: //由Hibernate返回的PO TUser user = (TUser)session.load(TUser.class,new Integer(1)); VO经过Hibernate进行处理,就变成了PO。 上面的示例代码session.save(user)中,我们把一个VO “user”传递给 Hibernate的Session.save方法进行保存。在save方法中,Hibernate对其进 行如下处理: 1. 在当前session所对应的实体容器(Entity Map)中查询是否存在user对象 的引用。 2. 如果引用存在,则直接返回user对象id,save过程结束. Hibernate中,针对每个Session有一个实体容器(实际上是一个Map对象), 如果此容器中已经保存了目标对象的引用,那么hibernate会认为此对象已经 与Session相关联。 对于save操作而言,如果对象已经与Session相