Hibernate.orgCommunity Documentation
3.5.6-Final
版权 © 2004 Red Hat, Inc.
September 15, 2010
在今日的企业环境中,把面向对象的软件和关系型数据库一起使用可能是相当麻烦和浪费时间的。Hibernate 是一个面向 Java 环境的对象/关系型数据库映射工具。对象/关系型数据库映射(object/relational mapping,ORM)这个术语表示一种技术,用来把对象模型表示的对象映射到基于 SQL 的关系模型数据结构中去。
Hibernate 不仅管理 Java 类到数据库表的映射(包括 Java 数据类型到 SQL 数据类型的映射),还提供数据查询和获取数据的方法,可以大幅度减少开发时对人工使用 SQL 和 JDBC 处理数据的时间。
Hibernate 的目标是对于开发者通常的数据持久化相关的编程任务,解放其中的 95%。对于以数据为中心的程序来说,它们往往只在数据库中使用存储过程来实现商业逻辑,Hibernate 可能不是最好的解决方案;对于那些在基于 Java 的中间层应用中,它们实现面向对象的业务模型和商业逻辑的应用,Hibernate 是最有用的。不管怎样,Hibernate 一定可以帮助你消除或者包装那些针对特定厂商的 SQL 代码,而且帮助你结果集从表格式的表示形式转换到一系列的对象中去。
如果你对 Hibernate 和对象/关系型数据库映射还是个新手,甚至对 Java 也不熟悉,请按照下面的步骤来学习。
Read 第 1 章 教程 for a tutorial with step-by-step instructions. The source code for the tutorial is included in the distribution in the doc/reference/tutorial/ directory.
Read 第 2 章 体系结构(Architecture) to understand the environments where Hibernate can be used.
查看 Hibernate 发行包中的 eg/ 目录,里面有个一简单的独立运行的程序。把你的 JDBC 驱动复制到 lib/ 目录并修改一下 etc/hibernate.properties,指定数据库的信息。然后进入命令行,切换到发行包的目录,输入 ant eg(使用 Ant),或者在 Windows 系统下使用 build eg。
Use this reference documentation as your primary source of information. Consider reading [JPwH] if you need more help with application design, or if you prefer a step-by-step tutorial. Also visit http://caveatemptor.hibernate.org and download the example application from [JPwH].
在 Hibernate 网站上可以找到问题和解答(FAQ)。
在 Hibernate 网站上还有第三方的演示、示例和教程的链接。
Hibernate 网站的社区是讨论关于设计模式以及很多整合方案(Tomcat、JBoss AS、Struts、EJB 等)的好地方。
如果你有任何问题,请使用 Hibernate 网站上链接的用户论坛。我们也提供一个 JIRA 问题追踪系统,来搜集 bug 报告和新的功能请求。如果对开发 Hibernate 有兴趣,请加入开发者的邮件列表。如果你对翻译本文档感兴趣,请通过开发者的邮件列表来联系我们。
商业开发、产品支持和 Hibernate 培训可以通过 JBoss Inc. 获得(请查阅: http://www.hibernate.org/SupportTraining/)。Hibernate 是一个专业的开源项目,也是 JBoss 企业级中间件系统(JBoss Enterprise Middleware System,JEMS)里的一个核心组件。
面向新用户,从一个简单的使用内存数据库的例子开始,本章提供对 Hibernate 的逐步介绍。本教程基于 Michael Gloegl 早期编写的手册。所有代码都包含在 tutorials/web 目录下。
本教程期望用户具备 Java 和 SQL 知识。如果你这方面的知识有限,我们建议你在学习 Hibernate 之前先好好了解这些技术。
本版本在源代码目录 tutorial/eg 下还包含另外一个例程。
在这个例子里,我们将设立一个小应用程序可以保存我们希望参加的活动(events)和这些活动主办方的相关信息。(译者注:在本教程的后面部分,我们将直接使用 event 而不是它的中文翻译“活动”,以免混淆。)
虽然你可以使用任何数据库,我们还是用 HSQLDB(一个用 Java 编写的内存数据库)来避免花费篇章对数据库服务器的安装/配置进行解释。
我们需要做的第一件事情是设置开发环境。我们将使用许多构建工具如 Maven 所鼓吹的“标准格式”。特别是 Maven,它的资源对这个格式(layout)有着很好的描述。因为本教程使用的是 web 应用程序,我么将创建和使用 src/main/java、src/main/resources 和 src/main/webapp 目录。
在本教程里我们将使用 Maven,利用其 transitive dependency 管理以及根据 Maven 描述符用 IDE 自动设置项目的能力。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion
>4.0.0</modelVersion>
<groupId
>org.hibernate.tutorials</groupId>
<artifactId
>hibernate-tutorial</artifactId>
<version
>1.0.0-SNAPSHOT</version>
<name
>First Hibernate Tutorial</name>
<build>
<!-- we dont want the version to be part of the generated war file name -->
<finalName
>${artifactId}</finalName>
</build>
<dependencies>
<dependency>
<groupId
>org.hibernate</groupId>
<artifactId
>hibernate-core</artifactId>
</dependency>
<!-- Because this is a web app, we also have a dependency on the servlet api. -->
<dependency>
<groupId
>javax.servlet</groupId>
<artifactId
>servlet-api</artifactId>
</dependency>
<!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend -->
<dependency>
<groupId
>org.slf4j</groupId>
<artifactId
>slf4j-simple</artifactId>
</dependency>
<!-- Hibernate gives you a choice of bytecode providers between cglib and javassist -->
<dependency>
<groupId
>javassist</groupId>
<artifactId
>javassist</artifactId>
</dependency>
</dependencies>
</project
>
It is not a requirement to use Maven. If you wish to use something else to build this tutorial (such as Ant), the layout will remain the same. The only change is that you will need to manually account for all the needed dependencies. If you use something like Ivy providing transitive dependency management you would still use the dependencies mentioned below. Otherwise, you'd need to grab all dependencies, both explicit and transitive, and add them to the project's classpath. If working from the Hibernate distribution bundle, this would mean hibernate3.jar, all artifacts in the lib/required directory and all files from either the lib/bytecode/cglib or lib/bytecode/javassist directory; additionally you will need both the servlet-api jar and one of the slf4j logging backends.
把这个文件保存为项目根目录下的 pom.xml。
接下来我们创建一个类,用来代表那些我们希望储存在数据库里的 event,这是一个具有一些属性的简单 JavaBean 类:
package org.hibernate.tutorial.domain;
import java.util.Date;
public class Event {
private Long id;
private String title;
private Date date;
public Event() {}
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
你可以看到这个类对属性的存取方法(getter and setter method)使用了标准 JavaBean 命名约定,同时把类属性(field)的访问级别设成私有的(private)。这是推荐的设计,但并不是必须的。Hibernate 也可以直接访问这些 field,而使用访问方法(accessor method)的好处是提供了重构时的健壮性(robustness)。
对一特定的 event, id 属性持有唯一的标识符(identifier)的值。如果我们希望使用 Hibernate 提供的所有特性,那么所有的持久化实体(persistent entity)类(这里也包括一些次要依赖类)都需要一个这样的标识符属性。而事实上,大多数应用程序(特别是 web 应用程序)都需要通过标识符来区别对象,所以你应该考虑使用标识符属性而不是把它当作一种限制。然而,我们通常不会操作对象的标识(identity),因此它的 setter 方法的访问级别应该声明 private。这样当对象被保存的时候,只有 Hibernate 可以为它分配标识符值。你可看到Hibernate可以直接访问 public,private 和 protected 的访问方法和 field。所以选择哪种方式完全取决于你,你可以使你的选择与你的应用程序设计相吻合。
所有的持久化类(persistent classes)都要求有无参的构造器,因为 Hibernate 必须使用 Java 反射机制来为你创建对象。构造器(constructor)的访问级别可以是 private,然而当生成运行时代理(runtime proxy)的时候则要求使用至少是 package 级别的访问控制,这样在没有字节码指令(bytecode instrumentation)的情况下,从持久化类里获取数据会更有效率。
把这个文件保存到 src/main/java/org/hibernate/tutorial/domain 目录下。
Hibernate 需要知道怎样去加载(load)和存储(store)持久化类的对象。这正是 Hibernate 映射文件发挥作用的地方。映射文件告诉 Hibernate 它应该访问数据库(database)里面的哪个表(table)及应该使用表里面的哪些字段(column)。
一个映射文件的基本结构看起来像这样:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.tutorial.domain">
[...]
</hibernate-mapping
>
注意 Hibernate 的 DTD 是非常复杂的。你的编辑器或者 IDE 里使用它来自动完成那些用来映射的 XML 元素(element)和属性(attribute)。你也可以在文本编辑器里打开 DTD — 这是最简单的方式来概览所有的元素和 attribute,并查看它们的缺省值以及注释。注意 Hibernate 不会从 web 加载 DTD 文件,但它会首先在应用程序的 classpath 中查找。DTD 文件已包括在 hibernate3.jar 里,同时也在 Hibernate 发布包的 src/ 目录下。
为缩短代码长度,在以后的例子里我们会省略 DTD 的声明。当然,在实际的应用程序中,DTD 声明是必需的。
在 hibernate-mapping 标签(tag)之间, 含有一个 class 元素。所有的持久化实体类(再次声明,或许接下来会有依赖类,就是那些次要的实体)都需要一个这样的映射,来把类对象映射到 SQL 数据库里的表:
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Event" table="EVENTS">
</class>
</hibernate-mapping
>
到目前为止,我们告诉了 Hibernate 怎样把 Events 类的对象持久化到数据库的 EVENTS 表里,以及怎样从 EVENTS 表加载到 Events 类的对象。每个实例对应着数据库表中的一行。现在我们将继续讨论有关唯一标识符属性到数据库表的映射。另外,由于我们不关心怎样处理这个标识符,我们就配置由 Hibernate 的标识符生成策略来产生代理主键字段:
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
</class>
</hibernate-mapping
>
id 元素是对 identifier 属性的声明。name="id" 映射属性声明了 JavaBean 属性的名称并告诉 Hibernate 使用 getId() 和 setId() 方法来访问这个属性。column 属性告诉 Hibernate EVENTS 表的哪个字段持有主键值。
嵌套的 generator 元素指定标识符的生成策略(也就是标识符值是怎么产生的)。在这个例子里,我们选择 native,它提供了取决于数据库方言的可移植性。Hibernate 数据库生成的、全局性唯一的以及应用程序分配的标识符。标识符值的生成也是 Hibernate 的扩展功能之一,你可以插入自己的策略。
native is no longer consider the best strategy in terms of portability. for further discussion, see 第 26.4 节 “标识符的生成”
最后我们在映射文件里面包含需要持久化属性的声明。默认情况下,类里面的属性都被视为非持久化的:
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
</class>
</hibernate-mapping
>
和 id 元素一样,property 元素的 name 属性告诉 Hibernate 使用哪个 getter 和 setter 方法。在此例中,Hibernate 会寻找 getDate()、setDate()、getTitle() 和 setTitle() 方法。
为什么 date 属性的映射含有 column attribute,而 title 却没有?当没有设定 column attribute 的时候,Hibernate 缺省地使用 JavaBean 的属性名作为字段名。对于 title,这样工作得很好。然而,date 在多数的数据库里,是一个保留关键字,所以我们最好把它映射成一个不同的名字。
另一有趣的事情是 title 属性缺少一个 type attribute。我们在映射文件里声明并使用的类型,却不是我们期望的那样,是 Java 数据类型,同时也不是 SQL 数据库的数据类型。这些类型就是所谓的 Hibernate 映射类型(mapping types),它们能把 Java 数据类型转换到 SQL 数据类型,反之亦然。再次重申,如果在映射文件中没有设置 type 属性的话,Hibernate 会自己试着去确定正确的转换类型和它的映射类型。在某些情况下这个自动检测机制(在 Java 类上使用反射机制)不会产生你所期待或需要的缺省值。date 属性就是个很好的例子,Hibernate 无法知道这个属性(java.util.Date 类型的)应该被映射成:SQL date,或 timestamp,还是 time 字段。在此例中,把这个属性映射成 timestamp 转换器,这样我们预留了日期和时间的全部信息。
当处理映射文件时,Hibernate 用反射(reflection)来决定这个映射类型。这需要时间和资源,所以如果你注重启动性能,你应该考虑显性地定义所用的类型。
把这个映射文件保存为 src/main/resources/org/hibernate/tutorial/domain/Event.hbm.xml。
此时,你应该有了持久化类和它的映射文件。现在是配置 Hibernate 的时候了。首先让我们设立 HSQLDB 使其运行在“服务器模式”。
数据在程序运行期间需要保持有效。
在开发的根目录下创建一个 data 目录 - 这是 HSQL DB 存储数据文件的地方。此时在 data 目录中运行 java -classpath ../lib/hsqldb.jar org.hsqldb.Server 就可启动数据库。你可以在 log 中看到它的启动,及绑定到 TCP/IP 套接字,这正是我们的应用程序稍后会连接的地方。如果你希望在本例中运行一个全新的数据库,就在窗口中按下 CTRL + C 来关闭 HSQL 数据库,并删除 data/ 目录下的所有文件,再重新启动 HSQL 数据库。
Hibernate 将为你的应用程序连接到数据库,所以它需要知道如何获取连接。在这个教程里,我们使用一个独立连接池(和 javax.sql.DataSource 相反)。Hibernate 支持两个第三方的开源 JDBC 连接池:c3p0 和 proxool。然而,在本教程里我们将使用 Hibernate 内置的连接池。
嵌入的 Hibernate 连接池不用于产品环境。它缺乏连接池里的几个功能。
为了保存 Hibernate 的配置,我们可以使用一个简单的 hibernate.properties 文件,或者一个稍微复杂的 hibernate.cfg.xml,甚至可以完全使用程序来配置 Hibernate。多数用户更喜欢使用 XML 配置文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class"
>org.hsqldb.jdbcDriver</property>
<property name="connection.url"
>jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username"
>sa</property>
<property name="connection.password"
></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size"
>1</property>
<!-- SQL dialect -->
<property name="dialect"
>org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class"
>thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class"
>org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql"
>true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto"
>update</property>
<mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration
>
请注意,这个配置文件指定了一个不同的 DTD。
注意这个 XML 配置使用了一个不同的 DTD。在这里,我们配置了 Hibernate 的SessionFactory — 一个关联于特定数据库全局的工厂(factory)。如果你要使用多个数据库,就要用多个的 <session-factory>,通常把它们放在多个配置文件中(为了更容易启动)。
签名 4 个 property 元素包含了 JDBC 连接所必需的配置。方言 property 元素指定了 Hibernate 生成的特定 SQL 语句。
In most cases, Hibernate is able to properly determine which dialect to use. See 第 26.3 节 “方言的使用” for more information.
最开始的 4 个 property 元素包含必要的 JDBC 连接信息。方言(dialect)的 property 元素指明 Hibernate 生成的特定 SQL 变量。你很快会看到,Hibernate 对持久化上下文的自动 session 管理就会派上用场。 打开 hbm2ddl.auto 选项将自动生成数据库模式(schema)- 直接加入数据库中。当然这个选项也可以被关闭(通过去除这个配置选项)或者通过 Ant 任务 SchemaExport 的帮助来把数据库 schema 重定向到文件中。最后,在配置中为持久化类加入映射文件。
把这个文件保存为 src/main/resources 目录下的 hibernate.cfg.xml。
我们将用 Maven 构建这个教程。你将需要安装 Maven;你可以从 Maven 下载页面获得 Maven。Maen 将读取我们先前创建的 /pom.xml 并知道执行基本的项目任务。首先,让我们运行 compile 目标来确保我们可以编译到目前为止的所有程序:
[hibernateTutorial]$ mvn compile [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Building First Hibernate Tutorial [INFO] task-segment: [compile] [INFO] ------------------------------------------------------------------------ [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Compiling 1 source file to /home/steve/projects/sandbox/hibernateTutorial/target/classes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2 seconds [INFO] Finished at: Tue Jun 09 12:25:25 CDT 2009 [INFO] Final Memory: 5M/547M [INFO] ------------------------------------------------------------------------
是时候来加载和储存一些 Event 对象了,但首先我们得编写一些基础的代码以完成设置。我们必须启动 Hibernate,此过程包括创建一个全局的 SessoinFactory,并把它储存在应用程序代码容易访问的地方。SessionFactory 可以创建并打开新的 Session。一个 Session 代表一个单线程的单元操作,org.hibernate.SessionFactory 则是个线程安全的全局对象,只需要被实例化一次。
我们将创建一个 HibernateUtil 辅助类(helper class)来负责启动 Hibernate 和更方便地操作 org.hibernate.SessionFactory。让我们来看一下它的实现:
package org.hibernate.tutorial.util;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
把这段代码保存为 src/main/java/org/hibernate/tutorial/util/HibernateUtil.java。
这个类不但在它的静态初始化过程(仅当加载这个类的时候被 JVM 执行一次)中产生全局的 org.hibernate.SessionFactory,而且隐藏了它使用了静态 singleton 的事实。它也可能在应用程序服务器中的 JNDI 查找 org.hibernate.SessionFactory。
如果你在配置文件中给 org.hibernate.SessionFactory 一个名字,在 它创建后,Hibernate 会试着把它绑定到 JNDI。要完全避免这样的代码,你也可以使用 JMX 部署,让具有 JMX 能力的容器来实例化 HibernateService 并把它绑定到 JNDI。这些高级可选项在后面的章节中会讨论到。
再次编译这个应用程序应该不会有问题。最后我们需要配置一个日志(logging)系统 — Hibernate 使用通用日志接口,允许你在 Log4j 和 JDK 1.4 日志之间进行选择。多数开发者更喜欢 Log4j:从 Hibernate 的发布包中(它在 etc/ 目录下)拷贝 log4j.properties 到你的 src 目录,与 hibernate.cfg.xml 放在一起。看一下配置示例,如果你希望看到更加详细的输出信息,你可以修改配置。默认情况下,只有 Hibernate 的启动信息才会显示在标准输出上。
示例的基本框架完成了 — 现在我们可以用 Hibernate 来做些真正的工作。
We are now ready to start doing some real work with Hibernate. Let's start by writing an EventManager class with a main() method:
package org.hibernate.tutorial;
import org.hibernate.Session;
import java.util.*;
import org.hibernate.tutorial.domain.Event;
import org.hibernate.tutorial.util.HibernateUtil;
public class EventManager {
public static void main(String[] args) {
EventManager mgr = new EventManager();
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
HibernateUtil.getSessionFactory().close();
}
private void createAndStoreEvent(String title, Date theDate) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
session.save(theEvent);
session.getTransaction().commit();
}
}
在 createAndStoreEvent() 来里我们创建了一个新的 Event 对象并把它传递给 Hibernate。现在 Hibernate 负责与 SQL 打交道,并把 INSERT 命令传给数据库。
A org.hibernate.Session is designed to represent a single unit of work (a single atomic piece of work to be performed). For now we will keep things simple and assume a one-to-one granularity between a Hibernate org.hibernate.Session and a database transaction. To shield our code from the actual underlying transaction system we use the Hibernate org.hibernate.Transaction API. In this particular case we are using JDBC-based transactional semantics, but it could also run with JTA.
sessionFactory.getCurrentSession() 是干什么的呢?首先,只要你持有 org.hibernate.SessionFactory,大可在任何时候、任何地点调用这个方法。getCurrentSession() 方法总会返回“当前的”工作单元。记得我们在 src/main/resources/hibernate.cfg.xml 中把这一配置选项调整为 "thread" 了吗?因此,因此,当前工作单元被绑定到当前执行我们应用程序的 Java 线程。但是,这并非是完全准确的,你还得考虑工作单元的生命周期范围(scope),它何时开始,又何时结束。
Hibernate 提供三种跟踪当前会话的方法。基于“线程”的方法不适合于产品环境,它仅用于 prototyping 和教学用途。后面将更详细地讨论会话跟踪。
org.hibernate.Session 在第一次被使用的时候,即第一次调用 getCurrentSession() 的时候,其生命周期就开始。然后它被 Hibernate 绑定到当前线程。当事务结束的时候,不管是提交还是回滚,Hibernate 会自动把 org.hibernate.Session 从当前线程剥离,并且关闭它。假若你再次调用 getCurrentSession(),你会得到一个新的 org.hibernate.Session,并且开始一个新的工作单元。
和工作单元的生命周期这个话题相关,Hibernate org.hibernate.Session 是否被应该用来执行多次数据库操作?上面的例子对每一次操作使用了一个 org.hibernate.Session,这完全是巧合,这个例子不是很复杂,无法展示其他方式。Hibernate org.hibernate.Session 的生命周期可以很灵活,但是你绝不要把你的应用程序设计成为每一次数据库操作都用一个新的 Hibernate org.hibernate.Session。因此就算下面的例子(它们都很简单)中你可以看到这种用法,记住每次操作一个 session 是一个反模式。在本教程的后面会展示一个真正的(web)程序。
See 第 12 章 事务和并发 for more information about transaction handling and demarcation. The previous example also skipped any error handling and rollback.
要运行它,我们将使用 Maven exec 插件以及必要的 classpath 设置来进行调用:mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="store"。
你可能需要先执行 mvn compile。
你应该会看到,编译以后,Hibernate 根据你的配置启动,并产生一大堆的输出日志。在日志最后你会看到下面这行:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
执行 HQL INSERT 语句的例子如下:
我们想要列出所有已经被存储的 events,就要增加一个条件分支选项到 main 方法中:
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
else if (args[0].equals("list")) {
List events = mgr.listEvents();
for (int i = 0; i < events.size(); i++) {
Event theEvent = (Event) events.get(i);
System.out.println(
"Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()
);
}
}
我们也增加一个新的 listEvents() 方法:
private List listEvents() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result = session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}
Here, we are using a Hibernate Query Language (HQL) query to load all existing Event objects from the database. Hibernate will generate the appropriate SQL, send it to the database and populate Event objects with the data. You can create more complex queries with HQL. See 第 15 章 HQL: Hibernate 查询语言 for more information.
现在我们可以再次用 Maven exec plugin - mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="list" 调用新的功能了。
我们已经映射了一个持久化实体类到表上。让我们在这个基础上增加一些类之间的关联。首先我们往应用程序里增加人(people)的概念,并存储他们所参与的一个 Event 列表。(译者注:与 Event 一样,我们在后面将直接使用 person 来表示“人”而不是它的中文翻译)
最初简单的 Person 类:
package org.hibernate.tutorial.domain;
public class Person {
private Long id;
private int age;
private String firstname;
private String lastname;
public Person() {}
// Accessor methods for all properties, private setter for 'id'
}
把它保存为文件 src/main/java/org/hibernate/tutorial/domain/Person.java。
然后,创建新的映射文件 src/main/resources/org/hibernate/tutorial/domain/Person.hbm.xml。
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
</class>
</hibernate-mapping
>
最后,把新的映射加入到 Hibernate 的配置中:
<mapping resource="events/Event.hbm.xml"/>
<mapping resource="events/Person.hbm.xml"/>
现在我们在这两个实体之间创建一个关联。显然,persons 可以参与一系列 events,而 events 也有不同的参加者(persons)。我们需要处理的设计问题是关联方向(directionality),阶数(multiplicity)和集合(collection)的行为。
我们将向 Person 类增加一连串的 events。那样,通过调用 aPerson.getEvents(),就可以轻松地导航到特定 person 所参与的 events,而不用去执行一个显式的查询。我们使用 Java 的集合类(collection):Set,因为 set 不包含重复的元素及与我们无关的排序。
public class Person {
private Set events = new HashSet();
public Set getEvents() {
return events;
}
public void setEvents(Set events) {
this.events = events;
}
}
在映射这个关联之前,先考虑一下此关联的另外一端。很显然,我们可以保持这个关联是单向的。或者,我们可以在 Event 里创建另外一个集合,如果希望能够双向地导航,如:anEvent.getParticipants()。从功能的角度来说,这并不是必须的。因为你总可以显式地执行一个查询,以获得某个特定 event 的所有参与者。这是个在设计时需要做出的选择,完全由你来决定,但此讨论中关于关联的阶数是清楚的:即两端都是“多”值的,我们把它叫做多对多(many-to-many)关联。因而,我们使用 Hibernate 的多对多映射:
<class name="Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="Event"/>
</set>
</class
>
Hibernate 支持各种各样的集合映射,<set> 使用的最为普遍。对于多对多关联(或叫 n:m 实体关系), 需要一个关联表(association table)。表里面的每一行代表从 person 到 event 的一个关联。表名是由 set 元素的 table 属性配置的。关联里面的标识符字段名,对于 person 的一端,是由 <key> 元素定义,而 event 一端的字段名是由 <many-to-many> 元素的 column 属性定义。你也必须告诉 Hibernate 集合中对象的类(也就是位于这个集合所代表的关联另外一端的类)。
因而这个映射的数据库 schema 是:
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | |
|_____________| |__________________| | PERSON |
| | | | |_____________|
| *EVENT_ID | <--> | *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
我们把一些 people 和 events 一起放到 EventManager 的新方法中:
private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
Event anEvent = (Event) session.load(Event.class, eventId);
aPerson.getEvents().add(anEvent);
session.getTransaction().commit();
}
在加载一 Person 和 Event 后,使用普通的集合方法就可容易地修改我们定义的集合。如你所见,没有显式的 update() 或 save(),Hibernate 会自动检测到集合已经被修改并需要更新回数据库。这叫做自动脏检查(automatic dirty checking),你也可以尝试修改任何对象的 name 或者 date 属性,只要他们处于持久化状态,也就是被绑定到某个 Hibernate 的 Session 上(如:他们刚刚在一个单元操作被加载或者保存),Hibernate 监视任何改变并在后台隐式写的方式执行 SQL。同步内存状态和数据库的过程,通常只在单元操作结束的时候发生,称此过程为清理缓存(flushing)。在我们的代码中,工作单元由数据库事务的提交(或者回滚)来结束——这是由 CurrentSessionContext 类的 thread 配置选项定义的。
当然,你也可以在不同的单元操作里面加载 person 和 event。或在 Session 以外修改不是处在持久化(persistent)状态下的对象(如果该对象以前曾经被持久化,那么我们称这个状态为脱管(detached))。你甚至可以在一个集合被脱管时修改它:
private void addPersonToEvent(Long personId, Long eventId) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session
.createQuery("select p from Person p left join fetch p.events where p.id = :pid")
.setParameter("pid", personId)
.uniqueResult(); // Eager fetch the collection so we can use it detached
Event anEvent = (Event) session.load(Event.class, eventId);
session.getTransaction().commit();
// End of first unit of work
aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached
// Begin second unit of work
Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
session2.beginTransaction();
session2.update(aPerson); // Reattachment of aPerson
session2.getTransaction().commit();
}
对 update 的调用使一个脱管对象重新持久化,你可以说它被绑定到一个新的单元操作上,所以在脱管状态下对它所做的任何修改都会被保存到数据库里。这也包括你对这个实体对象的集合所作的任何改动(增加/删除)。
这对我们当前的情形不是很有用,但它是非常重要的概念,你可以把它融入到你自己的应用程序设计中。在EventManager的 main 方法中添加一个新的动作,并从命令行运行它来完成我们所做的练习。如果你需要 person 及 event 的标识符 — 那就用 save() 方法返回它(你可能需要修改前面的一些方法来返回那个标识符):
else if (args[0].equals("addpersontoevent")) {
Long eventId = mgr.createAndStoreEvent("My Event", new Date());
Long personId = mgr.createAndStorePerson("Foo", "Bar");
mgr.addPersonToEvent(personId, eventId);
System.out.println("Added person " + personId + " to event " + eventId);
}
上面是个关于两个同等重要的实体类间关联的例子。像前面所提到的那样,在特定的模型中也存在其它的类和类型,这些类和类型通常是“次要的”。你已看到过其中的一些,像 int 或 String。我们称这些类为值类型(value type),它们的实例依赖(depend)在某个特定的实体上。这些类型的实例没有它们自己的标识(identity),也不能在实体间被共享(比如,两个 person 不能引用同一个 firstname 对象,即使他们有相同的 first name)。当然,值类型并不仅仅在 JDK 中存在(事实上,在一个 Hibernate 应用程序中,所有的 JDK 类都被视为值类型),而且你也可以编写你自己的依赖类,例如 Address,MonetaryAmount。
你也可以设计一个值类型的集合,这在概念上与引用其它实体的集合有很大的不同,但是在 Java 里面看起来几乎是一样的。
让我们在 Person 实体里添加一个电子邮件的集合。这将以 java.lang.String 实例的 java.util.Set 出现:
private Set emailAddresses = new HashSet();
public Set getEmailAddresses() {
return emailAddresses;
}
public void setEmailAddresses(Set emailAddresses) {
this.emailAddresses = emailAddresses;
}
这个 Set 的映射如下:
<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<key column="PERSON_ID"/>
<element type="string" column="EMAIL_ADDR"/>
</set
>
比较这次和此前映射的差别,主要在于 element 部分,这次并没有包含对其它实体引用的集合,而是元素类型为 String 的集合(在映射中使用小写的名字”string“是向你表明它是一个 Hibernate 的映射类型或者类型转换器)。和之前一样,set 元素的 table 属性决定了用于集合的表名。key 元素定义了在集合表中外键的字段名。element 元素的 column 属性定义用于实际保存 String 值的字段名。
看一下修改后的数据库 schema。
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | | ___________________
|_____________| |__________________| | PERSON | | |
| | | | |_____________| | PERSON_EMAIL_ADDR |
| *EVENT_ID | <--> | *EVENT_ID | | | |___________________|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE | | *EMAIL_ADDR |
|_____________| | FIRSTNAME | |___________________|
| LASTNAME |
|_____________|
你可以看到集合表的主键实际上是个复合主键,同时使用了两个字段。这也暗示了对于同一个 person 不能有重复的 email 地址,这正是 Java 里面使用 Set 时候所需要的语义(Set 里元素不能重复)。
你现在可以试着把元素加入到这个集合,就像我们在之前关联 person 和 event 的那样。其实现的 Java 代码是相同的:
private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
// adding to the emailAddress collection might trigger a lazy load of the collection
aPerson.getEmailAddresses().add(emailAddress);
session.getTransaction().commit();
}
这次我们没有使用 fetch 查询来初始化集合。因此,调用其 getter 方法会触发另一附加的 select 来初始化集合,这样我们才能把元素添加进去。检查 SQL log,试着通过预先抓取来优化它。
接下来我们将映射双向关联(bi-directional association)— 在 Java 里让 person 和 event 可以从关联的任何一端访问另一端。当然,数据库 schema 没有改变,我们仍然需要多对多的阶数。一个关系型数据库要比网络编程语言更加灵活,所以它并不需要任何像导航方向(navigation direction)的东西 — 数据可以用任何可能的方式进行查看和获取。
关系型数据库比网络编程语言更为灵活,因为它不需要方向导航,其数据可以用任何可能的方式进行查看和提取。
首先,把一个参与者(person)的集合加入 Event 类中:
private Set participants = new HashSet();
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants = participants;
}
在 Event.hbm.xml 里面也映射这个关联。
<set name="participants" table="PERSON_EVENT" inverse="true">
<key column="EVENT_ID"/>
<many-to-many column="PERSON_ID" class="events.Person"/>
</set
>
如你所见,两个映射文件里都有普通的 set 映射。注意在两个映射文件中,互换了 key 和 many-to-many 的字段名。这里最重要的是 Event 映射文件里增加了 set 元素的 inverse="true" 属性。
这意味着在需要的时候,Hibernate 能在关联的另一端 — Person 类得到两个实体间关联的信息。这将会极大地帮助你理解双向关联是如何在两个实体间被创建的。
首先请记住,Hibernate 并不影响通常的 Java 语义。 在单向关联的例子中,我们是怎样在 Person 和 Event 之间创建联系的?我们把 Event 实例添加到 Person 实例内的 event 引用集合里。因此很显然,如果我们要让这个关联可以双向地工作,我们需要在另外一端做同样的事情 - 把 Person 实例加入 Event 类内的 Person 引用集合。这“在关联的两端设置联系”是完全必要的而且你都得这么做。
许多开发人员防御式地编程,创建管理关联的方法来保证正确的设置了关联的两端,比如在 Person 里:
protected Set getEvents() {
return events;
}
protected void setEvents(Set events) {
this.events = events;
}
public void addToEvent(Event event) {
this.getEvents().add(event);
event.getParticipants().add(this);
}
public void removeFromEvent(Event event) {
this.getEvents().remove(event);
event.getParticipants().remove(this);
}
注意现在对于集合的 get 和 set 方法的访问级别是 protected — 这允许在位于同一个包(package)中的类以及继承自这个类的子类可以访问这些方法,但禁止其他任何人的直接访问,避免了集合内容的混乱。你应尽可能地在另一端也把集合的访问级别设成 protected。
inverse 映射属性究竟表示什么呢?对于你和 Java 来说,一个双向关联仅仅是在两端简单地正确设置引用。然而,Hibernate 并没有足够的信息去正确地执行 INSERT 和 UPDATE 语句(以避免违反数据库约束),所以它需要一些帮助来正确的处理双向关联。把关联的一端设置为 inverse 将告诉 Hibernate 忽略关联的这一端,把这端看成是另外一端的一个镜象(mirror)。这就是所需的全部信息,Hibernate 利用这些信息来处理把一个有向导航模型转移到数据库 schema 时的所有问题。你只需要记住这个直观的规则:所有的双向关联需要有一端被设置为 inverse。在一对多关联中它必须是代表多(many)的那端。而在多对多(many-to-many)关联中,你可以任意选取一端,因为两端之间并没有差别。
Hibernate web 应用程序使用 Session 和 Transaction 的方式几乎和独立应用程序是一样的。但是,有一些常见的模式(pattern)非常有用。现在我们编写一个 EventManagerServlet。这个 servlet 可以列出数据库中保存的所有的 events,还提供一个 HTML 表单来增加新的 events。
这个 servlet 只处理 HTTP GET 请求,因此,我们要实现的是 doGet() 方法:
package org.hibernate.tutorial.web;
// Imports
public class EventManagerServlet extends HttpServlet {
protected void doGet(
HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
SimpleDateFormat dateFormatter = new SimpleDateFormat( "dd.MM.yyyy" );
try {
// Begin unit of work
HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
// Process request and render page...
// End unit of work
HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
}
catch (Exception ex) {
HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
if ( ServletException.class.isInstance( ex ) ) {
throw ( ServletException ) ex;
}
else {
throw new ServletException( ex );
}
}
}
}
把这个 servlet 保存为 src/main/java/org/hibernate/tutorial/web/EventManagerServlet.java。
我们称这里应用的模式为每次请求一个 session(session-per-request)。当有请求到达这个 servlet 的时候,通过对 SessionFactory 的第一次调用,打开一个新的 Hibernate Session。然后启动一个数据库事务 — 所有的数据访问都是在事务中进行,不管是读还是写(我们在应用程序中不使用 auto-commit 模式)。
不要为每次数据库操作都使用一个新的 Hibernate Session。将 Hibernate Session 的范围设置为整个请求。要用 getCurrentSession(),这样它自动会绑定到当前 Java 线程。
下一步,对请求的可能动作进行处理,渲染出反馈的 HTML。我们很快就会涉及到那部分。
最后,当处理与渲染都结束的时候,这个工作单元就结束了。假若在处理或渲染的时候有任何错误发生,会抛出一个异常,回滚数据库事务。这样,session-per-request 模式就完成了。为了避免在每个 servlet 中都编写事务边界界定的代码,可以考虑写一个 servlet 过滤器(filter)来更好地解决。关于这一模式的更多信息,请参阅 Hibernate 网站和 Wiki,这一模式叫做 Open Session in View — 只要你考虑用JSP来渲染你的视图(view),而不是在servlet中,你就会很快用到它。
我们来实现处理请求以及渲染页面的工作。
// Write HTML header
PrintWriter out = response.getWriter();
out.println("<html
><head
><title
>Event Manager</title
></head
><body
>");
// Handle actions
if ( "store".equals(request.getParameter("action")) ) {
String eventTitle = request.getParameter("eventTitle");
String eventDate = request.getParameter("eventDate");
if ( "".equals(eventTitle) || "".equals(eventDate) ) {
out.println("<b
><i
>Please enter event title and date.</i
></b
>");
}
else {
createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
out.println("<b
><i
>Added event.</i
></b
>");
}
}
// Print page
printEventForm(out);
listEvents(out, dateFormatter);
// Write HTML footer
out.println("</body
></html
>");
out.flush();
out.close();
必须承认,这种编码风格把 Java 和 HTML 混在一起,在更复杂的应用程序里不应该大量使用 — 记住,在本章里我们仅仅是展示了 Hibernate 的基本概念。这段代码打印出了 HTML 页眉和页脚,在这个页面里,还打印了一个输入 events 条目的表单单并列出了数据库里的有的 events。第一个方法微不足道,仅仅是输出 HTML:
private void printEventForm(PrintWriter out) {
out.println("<h2
>Add new event:</h2
>");
out.println("<form
>");
out.println("Title: <input name='eventTitle' length='50'/><br/>");
out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>");
out.println("<input type='submit' name='action' value='store'/>");
out.println("</form
>");
}
listEvents() 方法使用绑定到当前线程的 Hibernate Session 来执行查询:
private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {
List result = HibernateUtil.getSessionFactory()
.getCurrentSession().createCriteria(Event.class).list();
if (result.size()
> 0) {
out.println("<h2
>Events in database:</h2
>");
out.println("<table border='1'
>");
out.println("<tr
>");
out.println("<th
>Event title</th
>");
out.println("<th
>Event date</th
>");
out.println("</tr
>");
Iterator it = result.iterator();
while (it.hasNext()) {
Event event = (Event) it.next();
out.println("<tr
>");
out.println("<td
>" + event.getTitle() + "</td
>");
out.println("<td
>" + dateFormatter.format(event.getDate()) + "</td
>");
out.println("</tr
>");
}
out.println("</table
>");
}
}
最后,store 动作会被导向到 createAndStoreEvent() 方法,它也使用当前线程的 Session:
protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
HibernateUtil.getSessionFactory()
.getCurrentSession().save(theEvent);
}
大功告成,这个 servlet 写完了。Hibernate 会在单一的 Session 和 Transaction 中处理到达的 servlet 请求。如同在前面的独立应用程序中那样,Hibernate 可以自动的把这些对象绑定到当前运行的线程中。这给了你用任何你喜欢的方式来对代码分层及访问 SessionFactory 的自由。通常,你会用更加完备的设计,把数据访问代码转移到数据访问对象中(DAO 模式)。请参见 Hibernate Wiki,那里有更多的例子。
要部署这个应用程序以进行测试,我们必须出具一个 Web ARchive (WAR)。首先我们必须定义 WAR 描述符为 src/main/webapp/WEB-INF/web.xml。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name
>Event Manager</servlet-name>
<servlet-class
>org.hibernate.tutorial.web.EventManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name
>Event Manager</servlet-name>
<url-pattern
>/eventmanager</url-pattern>
</servlet-mapping>
</web-app
>
在你的开发目录中,调用 ant war 来构建、打包,然后把 hibernate-tutorial.war 文件拷贝到你的 tomcat 的 webapps 目录下。假若你还没安装 Tomcat,就去下载一个,按照指南来安装。对此应用的发布,你不需要修改任何 Tomcat 的配置。
If you do not have Tomcat installed, download it from http://tomcat.apache.org/ and follow the installation instructions. Our application requires no changes to the standard Tomcat configuration.
在部署完,启动 Tomcat 之后,通过 http://localhost:8080/hibernate-tutorial/eventmanager 进行访问你的应用,在第一次 servlet 请求发生时,请在 Tomcat log 中确认你看到 Hibernate 被初始化了(HibernateUtil 的静态初始化器被调用),假若有任何异常抛出,也可以看到详细的输出。
本章覆盖了如何编写一个简单独立的 Hibernate 命令行应用程序及小型的 Hibernate web 应用程序的基本要素。更多的教程可以在 website 上找到。
下面的图表提供了 Hibernate 体系结构的高层视图:

提供 Hibernate 所有运行时体系结构的更多细节不是本文档的范畴。由于 Hibernate 非常灵活,且支持多种应用方案, 所以我们这只描述一下两种极端的情况:“最小”和“全面解决”的体系结构方案。
下图演示了 Hibernate 如何使用数据库和配置信息来为应用程序提供持久化服务(以及持久的对象)。
“最小”的体系结构方案,要求应用程序提供自己的 JDBC 连接并管理自己的事务。这种方案使用了Hibernate API 的最小子集:

“全面解决”的体系结构方案,将应用层从底层的 JDBC/JTA API 中抽象出来,而让 Hibernate 来处理这些细节。

图中各个对象的定义如下:
org.hibernate.SessionFactory)针对单个数据库映射关系经过编译后的内存镜像,是线程安全的(不可变)。 作为 Session 的工厂和 ConnectionProvider 的客户。SessionFactory 可以在进程或集群的级别上,为那些事务之间可以重用的数据提供可选的二级缓存。
org.hibernate.Session)表示应用程序与持久储存层之间交互操作的一个单线程对象,此对象生存期很短。其隐藏了 JDBC 连接,也是 Transaction 的工厂。它会持有一个针对持久化对象的必选(第一级)缓存,在遍历对象图或者根据持久化标识查找对象时会用到。
带有持久化状态的、具有业务功能的单线程对象,此对象生存期很短。这些对象可能是普通的JavaBeans/POJO,唯一特殊的是他们正与(仅仅一个)Session 相关联。一旦这个 Session 被关闭,这些对象就会脱离持久化状态,这样就可被应用程序的任何层自由使用(例如,用作跟表示层打交道的数据传输对象)。
那些目前没有与 Session关联的持久化类实例。他们可能是在被应用程序实例化后,尚未进行持久化的对象。也可能是因为实例化他们的 Session 已经被关闭而脱离持久化的对象。
org.hibernate.Transaction)(可选的)应用程序用来指定原子操作单元范围的对象,它是单线程的,生命周期很短。它通过抽象将应用从底层具体的 JDBC、JTA 以及 CORBA 事务隔离开。某些情况下,一个 Session 之内可能包含多个 Transaction 对象。尽管是否使用该对象是可选的,但无论是使用底层的 API 还是使用 Transaction 对象,事务边界的开启与关闭是必需的。
org.hibernate.connection.ConnectionProvider)(可选的)生成 JDBC 连接的工厂(同时也起到连接池的作用)。它通过抽象将应用从底层的 Datasource 或 DriverManager 隔离开。仅供开发者扩展/实现用,并不开放给应用程序使用。
org.hibernate.TransactionFactory)(可选的)生成 Transaction 对象实例的工厂。仅供开发者扩展/实现用,并不开发能够给应用程序使用。
Hibernate 提供了很多可选的扩展接口,你可以通过实现它们来定制你的持久层的行为。具体请参考 API 文档。
在特定“最小”的体系结构中,应用程序可能绕过 Transaction/TransactionFactory 以及 ConnectionProvider 等 API 直接跟 JTA 或 JDBC 打交道。
一个持久化类的实例可能处于三种不同状态中的某一种。这三种状态的定义则与所谓的持久化上下文(persistence context)有关。Hibernate 的 Session 对象就是这个所谓的持久化上下文。这三种不同的状态如下:
该实例从未与任何持久化上下文关联过。它没有持久化标识(相当于主键值)。
实例目前与某个持久化上下文有关联。它拥有持久化标识(相当于主键值),并且可能在数据库中有一个对应的行。对于某一个特定的持久化上下文,Hibernate 保证 持久化标识与 Java 标识(其值代表对象在内存中的位置)等价。
实例曾经与某个持久化上下文发生过关联,不过那个上下文被关闭了,或者这个实例是被序列化(serialize)到另外的进程。它拥有持久化标识,并且在数据库中可能存在一个对应的行。对于脱管状态的实例,Hibernate 不保证任何持久化标识和 Java 标识的关系。
JMX 是管理 Java 组件的 J2EE 标准。Hibernate 可以通过一个 JMX 标准服务来管理。在这个发行版本中,我们提供了一个 MBean 接口的实现,即 org.hibernate.jmx.HibernateService。
想要看如何在 JBoss 应用服务器上将 Hibernate 部署为一个 JMX 服务的例子,您可以参考《JBoss 用户指南》。如果你使用 JMX 来部署 Hibernate,JBoss AS 也提供如下好处:
会话管理: Hibernate 的 Session 对象的生命周期可以 自动跟一个 JTA 事务边界绑定。这意味着你无需手工开关 Session 了, 这项工作会由 JBoss EJB 拦截器来完成。你再也不用担心你的代码中的事务边界了(除非你想利用 Hibernate 提供可选的 Transaction API 来自己写一个便于移植的的持久层)。你通过调用 HibernateContext 来访问 Session。
HAR 部署: 通常情况下,你会使用 JBoss 的服务部署描述符(在 EAR 或/和 SAR 文件中)来部署 Hibernate JMX 服务。这种部署方式支持所有常见的 Hibernate SessionFactory 的配置选项。不过,你仍需在部署描述符中,列出你所有的映射文的名字。如果你使用 HAR 部署方式, JBoss 会自动探测出你的 HAR 文件中所有的映射文件。
这些选项更多的描述,请参考 JBoss 应用程序用户指南。
Another feature available as a JMX service is runtime Hibernate statistics. See 第 3.4.6 节 “Hibernate 的统计(statistics)机制” for more information.
Hibernate 也可以被配置为一个 JCA 连接器(JCA connector)。更多信息请参看网站。请注意,Hibernate 对 JCA 的支持,仍处于实验性阶段。
使用 Hibernate 的大多数应用程序需要某种形式的“上下文相关的”会话,特定的会话在整个特定的上下文范围内始终有效。然而,对不同类型的应用程序而言,要为什么是组成这种“上下文”下一个定义通常是困难的;不同的上下文对“当前”这个概念定义了不同的范围。在 3.0 版本之前,使用 Hibernate 的程序要么采用自行编写的基于 ThreadLocal 的上下文会话,要么采用 HibernateUtil 这样的辅助类,要么采用第三方框架(比如 Spring 或 Pico),它们提供了基于代理(proxy)或者基于拦截器(interception)的上下文相关的会话。
从 3.0.1 版本开始,Hibernate 增加了 SessionFactory.getCurrentSession() 方法。一开始,它假定了采用 JTA 事务,JTA 事务定义了当前 session 的范围和上下文(scope 和 context)。因为有好几个独立的 JTA TransactionManager 实现稳定可用,不论是否被部署到一个 J2EE 容器中,大多数(假若不是所有的)应用程序都应该采用 JTA 事务管理。基于这一点,采用 JTA 的上下文相关的会话可以满足你一切需要。
更好的是,从 3.1 开始,SessionFactory.getCurrentSession() 的后台实现是可拔插的。因此,我们引入了新的扩展接口(org.hibernate.context.CurrentSessionContext)和新的配置参数(hibernate.current_session_context_class),以便对什么是当前会话的范围(scope)和上下文(context)的定义进行拔插。
请参阅 org.hibernate.context.CurrentSessionContext 接口的 Javadoc,那里有关于它的契约的详细讨论。它定义了单一的方法,currentSession(),特定的实现用它来负责跟踪当前的上下文相关的会话。Hibernate 内置了此接口的三种实现:
org.hibernate.context.JTASessionContext:当前会话根据 JTA 来跟踪和界定。这和以前的仅支持 JTA 的方法是完全一样的。详情请参阅 Javadoc。
org.hibernate.context.ThreadLocalSessionContext:当前会话通过当前执行的线程来跟踪和界定。详情也请参阅 Javadoc。
org.hibernate.context.ManagedSessionContext:当前会话通过当前执行的线程来跟踪和界定。但是,你需要负责使用这个类的静态方法将 Session 实例绑定、或者取消绑定,它并不会打开(open)、flush 或者关闭(close)任何 Session。
The first two implementations provide a "one session - one database transaction" programming model. This is also known and used as session-per-request. The beginning and end of a Hibernate session is defined by the duration of a database transaction. If you use programmatic transaction demarcation in plain JSE without JTA, you are advised to use the Hibernate Transaction API to hide the underlying transaction system from your code. If you use JTA, you can utilize the JTA interfaces to demarcate transactions. If you execute in an EJB container that supports CMT, transaction boundaries are defined declaratively and you do not need any transaction or session demarcation operations in your code. Refer to 第 12 章 事务和并发 for more information and code examples.
hibernate.current_session_context_class 配置参数定义了应该采用哪个 org.hibernate.context.CurrentSessionContext 实现。注意,为了向下兼容,如果未配置此参数,但是存在 org.hibernate.transaction.TransactionManagerLookup 的配置,Hibernate 会采用org.hibernate.context.JTASessionContext。一般而言,此参数的值指明了要使用的实现类的全名,但那三种内置的实现可以使用简写,即 "jta"、"thread" 和 "managed"。
由于 Hibernate 是为了能在各种不同环境下工作而设计的,因此存在着大量的配置参数。幸运的是多数配置参数都有比较直观的默认值,并有随 Hibernate 一同分发的配置样例 hibernate.properties(位于 etc/)来展示各种配置选项。所需做的仅仅是将这个样例文件复制到类路径(classpath)下并进行定制。
org.hibernate.cfg.Configuration 实例代表了一个应用程序中 Java 类型到SQL数据库映射的完整集合。org.hibernate.cfg.Configuration 被用来构建一个(不可变的(immutable))org.hibernate.SessionFactory。映射定义则由不同的 XML 映射定义文件编译而来。
你可以直接实例化 org.hibernate.cfg.Configuration 来获取一个实例,并为它指定 XML 映射定义文件。如果映射定义文件在类路径(classpath)中,请使用 addResource()。例如:
Configuration cfg = new Configuration()
.addResource("Item.hbm.xml")
.addResource("Bid.hbm.xml");
一个替代方法(有时是更好的选择)是,指定被映射的类,让 Hibernate 帮你寻找映射定义文件:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class);
Hibernate 将会在类路径(classpath)中寻找名字为 /org/hibernate/auction/Item.hbm.xml 和 /org/hibernate/auction/Bid.hbm.xml 映射定义文件。这种方式消除了任何对文件名的硬编码(hardcoded)。
org.hibernate.cfg.Configuration
> 也允许你指定配置属性。例如:
Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class)
.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect")
.setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test")
.setProperty("hibernate.order_updates", "true");
当然这不是唯一的传递 Hibernate 配置属性的方式,其他可选方式还包括:
传一个 java.util.Properties 实例给 Configuration.setProperties()。
将 hibernate.properties 放置在类路径(classpath)的根目录下(root directory)。
通过 java -Dproperty=value 来设置系统(System)属性。
在 hibernate.cfg.xml 中加入元素 <property>(稍后讨论)。
如果你想快速上路,hibernate.properties 就是最容易的途径。
org.hibernate.cfg.Configuration 实例被设计成启动期间(startup-time)对象,一旦 SessionFactory 创建完成它就被丢弃了。
当所有映射定义被 org.hibernate.cfg.Configuration 解析后,应用程序必须获得一个用于构造 org.hibernate.Session 实例的工厂。这个工厂将被应用程序的所有线程共享:
SessionFactory sessions = cfg.buildSessionFactory();
Hibernate 允许你的应用程序创建多个 org.hibernate.SessionFactory 实例。这对 使用多个数据库的应用来说很有用。
通常你希望 org.hibernate.SessionFactory 来为你创建和缓存(pool)JDBC 连接。如果你采用这种方式,只需要如下例所示那样,打开一个 org.hibernate.Session:
Session session = sessions.openSession(); // open a new Session
一旦你需要进行数据访问时,就会从连接池(connection pool)获得一个 JDBC 连接。
为了使这种方式工作起来,我们需要向 Hibernate 传递一些 JDBC 连接的属性。所有 Hibernate 属性的名字和语义都在 org.hibernate.cfg.Environment 中定义。我们现在将描述 JDBC 连接配置中最重要的设置。
如果你设置如下属性,Hibernate 将使用 java.sql.DriverManager 来获得(和缓存)JDBC 连接:
表 3.1. Hibernate JDBC 属性
| 属性名 | 用途 |
|---|---|
| hibernate.connection.driver_class | JDBC driver class |
| hibernate.connection.url | JDBC URL |
| hibernate.connection.username | database user |
| hibernate.connection.password | database user password |
| hibernate.connection.pool_size | maximum number of pooled connections |
但 Hibernate 自带的连接池算法相当不成熟。它只是为了让你快些上手,并不适合用于产品系统或性能测试中。 出于最佳性能和稳定性考虑你应该使用第三方的连接池。只需要用特定连接池的设置替换 hibernate.connection.pool_size 即可。这将关闭 Hibernate 自带的连接池。例如,你可能会想用 C3P0。
C3P0 是一个随 Hibernate 一同分发的开源的 JDBC 连接池,它位于 lib目录下。 如果你设置了 hibernate.c3p0.* 相关的属性,Hibernate将使用 C3P0ConnectionProvider 来缓存 JDBC 连接。如果你更原意使用 Proxool,请参考发行包中的 hibernate.properties 并到 Hibernate 网站获取更多的信息。
这是一个使用 C3P0 的 hibernate.properties 样例文件:
hibernate.connection.driver_class = org.postgresql.Driver hibernate.connection.url = jdbc:postgresql://localhost/mydatabase hibernate.connection.username = myuser hibernate.connection.password = secret hibernate.c3p0.min_size=5 hibernate.c3p0.max_size=20 hibernate.c3p0.timeout=1800 hibernate.c3p0.max_statements=50 hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
为了能在应用程序服务器(application server)中使用 Hibernate,应当总是将 Hibernate 配置成从注册在 JNDI 中的 Datasource 处获得连接,你至少需要设置下列属性中的一个:
表 3.2. Hibernate 数据源属性
| 属性名 | 用途 |
|---|---|
| hibernate.connection.datasource | datasource JNDI name |
| hibernate.jndi.url | JNDI 提供者的 URL(可选) |
| hibernate.jndi.class | JNDI InitialContextFactory 类(可选) |
| hibernate.connection.username | 数据库用户(可选) |
| hibernate.connection.password | 数据库密码(可选) |
这是一个使用应用程序服务器提供的 JNDI 数据源的 hibernate.properties 样例文件:
hibernate.connection.datasource = java:/comp/env/jdbc/test
hibernate.transaction.factory_class = \
org.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
org.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect从 JNDI 数据源获得的 JDBC 连接将自动参与到应用程序服务器中容器管理的事务(container-managed transactions)中去。
任何连接(connection)属性的属性名都要以 "hibernate.connnection" 开头。例如,你可能会使用 hibernate.connection.charSet 来指定 charSet 连接属性。
通过实现 org.hibernate.connection.ConnectionProvider 接口,你可以定义属于你自己的获得JDBC连接的插件策略。通过设置hibernate.connection.provider_class,你可以选择一个自定义的实现。
有大量属性能用来控制 Hibernate 在运行期的行为。它们都是可选的,并拥有适当的默认值。
其中一些属性是"系统级(system-level)的"。系统级属性只能通过java -Dproperty=value 或 hibernate.properties 来设置,而不能用上面描述的其他方法来设置。
表 3.3. Hibernate 配置属性
| 属性名 | 用途 |
|---|---|
| hibernate.dialect | 允许 Hibernate 针对特定的关系数据库生成优化的 SQL 的 org.hibernate.dialect.Dialect 的类名。 例如: 在大多数情况下,Hibernate 可以根据 JDBC 驱动返回的 |
| hibernate.show_sql | 输出所有 SQL 语句到控制台。有一个另外的选择是把 org.hibernate.SQL 这个 log category设为 debug。 例如: |
| hibernate.format_sql | 在 log 和 console 中打印出更漂亮的 SQL。 例如: |
| hibernate.default_schema | 在生成的 SQL 中,将给定的 schema/tablespace 附加于非全限定名的表名上。 例如: |
| hibernate.default_catalog | 在生成的 SQL 中,将给定的 catalog 附加于非全限定名的表名上。 例如: |
| hibernate.session_factory_name | org.hibernate.SessionFactory 创建后,将自动使用这个名字绑定到 JNDI 中。 例如: |
| hibernate.max_fetch_depth | 为单向关联(一对一,多对一)的外连接抓取(outer join fetch)树设置最大深度。值为 0 意味着将关闭默认的外连接抓取。 例如: 建议在 |
| hibernate.default_batch_fetch_size | 为 Hibernate 关联的批量抓取设置默认数量。 例如:建议的取值为 |
| hibernate.default_entity_mode | 为由这个 SessionFactory 打开的所有 Session 指定默认的实体表现模式。 取值 |
| hibernate.order_updates | 强制 Hibernate 按照被更新数据的主键,为 SQL 更新排序。这么做将减少在高并发系统中事务的死锁。 例如: |
| hibernate.generate_statistics | 如果开启,Hibernate 将收集有助于性能调节的统计数据。 例如: |
| hibernate.use_identifier_rollback | 如果开启,在对象被删除时生成的标识属性将被重设为默认值。 例如: |
| hibernate.use_sql_comments | 如果开启,Hibernate 将在 SQL 中生成有助于调试的注释信息,默认值为 false。 例如: |
表 3.4. Hibernate JDBC 和连接(connection)属性
| 属性名 | 用途 |
|---|---|
| hibernate.jdbc.fetch_size | 非零值,指定 JDBC 抓取数量的大小(调用 Statement.setFetchSize())。 |
| hibernate.jdbc.batch_size | 非零值,允许 Hibernate 使用 JDBC2 的批量更新。 例如:建议取 |
| hibernate.jdbc.batch_versioned_data | Set this property to true if your JDBC driver returns correct row counts from executeBatch(). It is usually safe to turn this option on. Hibernate will then use batched DML for automatically versioned data. Defaults to false. 例如: |
| hibernate.jdbc.factory_class | 选择一个自定义的 Batcher。多数应用程序不需要这个配置属性。 例如: |
| hibernate.jdbc.use_scrollable_resultset | 允许 Hibernate 使用 JDBC2 的可滚动结果集。只有在使用用户提供的 JDBC 连接时,这个选项才是必要的,否则 Hibernate 会使用连接的元数据。 例如: |
| hibernate.jdbc.use_streams_for_binary | 在 JDBC 读写 binary 或 serializable 的类型时使用流(stream)(系统级属性)。 例如: |
| hibernate.jdbc.use_get_generated_keys | 在数据插入数据库之后,允许使用 JDBC3 PreparedStatement.getGeneratedKeys() 来获取数据库生成的 key(键)。需要 JDBC3+ 驱动和 JRE1.4+,如果你的数据库驱动在使用 Hibernate 的标识生成器时遇到问题,请将此值设为 false。默认情况下将使用连接的元数据来判定驱动的能力。 例如: |
| hibernate.connection.provider_class | 自定义 ConnectionProvider 的类名,此类用来向 Hibernate 提供 JDBC 连接。 例如: |
| hibernate.connection.isolation | 设置 JDBC 事务隔离级别。查看 java.sql.Connection 来了解各个值的具体意义,但请注意多数数据库都不支持所有的隔离级别。 例如: |
| hibernate.connection.autocommit | 允许被缓存的 JDBC 连接开启自动提交(autocommit)(不推荐)。 例如: |
| hibernate.connection.release_mode | 指定 Hibernate 在何时释放 JDBC 连接。默认情况下,直到 Session 被显式关闭或被断开连接时,才会释放 JDBC 连接。对于应用程序服务器的 JTA 数据源,你应当使用 after_statement,这样在每次 JDBC 调用后,都会主动的释放连接。对于非 JTA 的连接,使用 after_transaction 在每个事务结束时释放连接是合理的。auto 将为 JTA 和 CMT 事务策略选择 after_statement,为JDBC事务策略选择 after_transaction。 例如: This setting only affects |
| hibernate.connection.<propertyName> | 把 JDBC 属性 <propertyName> 传递给 DriverManager.getConnection()。 |
| hibernate.jndi.<propertyName> | 把 <propertyName> 属性传递给 JNDI InitialContextFactory。 |
表 3.5. Hibernate 缓存属性
| 属性名 | 用途 |
|---|---|
hibernate.cache.provider_class
| 自定义的 CacheProvider 的类名。 例如: |
hibernate.cache.use_minimal_puts
| 以频繁的读操作为代价,优化二级缓存来最小化写操作。在 Hibernate3 中,这个设置对的集群缓存非常有用,对集群缓存的实现而言,默认是开启的。 例如: |
hibernate.cache.use_query_cache
| 允许查询缓存,个别查询仍然需要被设置为可缓存的。 例如: |
hibernate.cache.use_second_level_cache
| 能用来完全禁止使用二级缓存。对那些在类的映射定义中指定 <cache> 的类,会默认开启二级缓存。 例如: |
hibernate.cache.query_cache_factory
| 自定义实现 QueryCache 接口的类名,默认为内建的 StandardQueryCache。 例如: |
hibernate.cache.region_prefix
| 二级缓存区域名的前缀。 例如: |
hibernate.cache.use_structured_entries
| 强制 Hibernate 以更人性化的格式将数据存入二级缓存。 例如: |
表 3.6. Hibernate 事务属性
| 属性名 | 用途 |
|---|---|
hibernate.transaction.factory_class
| 一个 TransactionFactory 的类名,用于 Hibernate Transaction API(默认为 JDBCTransactionFactory)。 例如: |
jta.UserTransaction
| 一个 JNDI 名字,被 JTATransactionFactory 用来从应用服务器获取 JTA UserTransaction。 例如: |
hibernate.transaction.manager_lookup_class
| 一个 TransactionManagerLookup 的类名 — 当使用 JVM 级缓存,或在 JTA 环境中使用 hilo 生成器的时候需要该类。 例如: |
hibernate.transaction.flush_before_completion
| If enabled, the session will be automatically flushed during the before completion phase of the transaction. Built-in and automatic session context management is preferred, see 第 2.5 节 “上下文相关的会话(Contextual Session)”. 例如: |
hibernate.transaction.auto_close_session
| If enabled, the session will be automatically closed during the after completion phase of the transaction. Built-in and automatic session context management is preferred, see 第 2.5 节 “上下文相关的会话(Contextual Session)”. 例如: |
表 3.7. 其他属性
| 属性名 | 用途 |
|---|---|
hibernate.current_session_context_class
| Supply a custom strategy for the scoping of the "current" Session. See 第 2.5 节 “上下文相关的会话(Contextual Session)” for more information about the built-in strategies. 例如: |
hibernate.query.factory_class
| 选择 HQL 解析器的实现。 例如: |
hibernate.query.substitutions
| 将 Hibernate 查询中的符号映射到 SQL 查询中的符号(符号可能是函数名或常量名字)。 例如: |
hibernate.hbm2ddl.auto
| 在 SessionFactory 创建时,自动检查数据库结构,或者将数据库 schema 的 DDL 导出到数据库。使用 create-drop 时,在显式关闭 SessionFactory 时,将删除掉数据库 schema。 例如: |
hibernate.bytecode.use_reflection_optimizer
|
Enables the use of bytecode manipulation instead of runtime reflection. This is a System-level property and cannot be set in 例如: |
hibernate.bytecode.provider
| Both javassist or cglib can be used as byte manipulation engines; the default is e.g. |
你应当总是为你的数据库将 hibernate.dialect 属性设置成正确的 org.hibernate.dialect.Dialect 子类。如果你指定一种方言,Hibernate 将为上面列出的一些属性使用合理的默认值,这样你就不用手工指定它们。
表 3.8. Hibernate SQL 方言(hibernate.dialect)
| RDBMS | Dialect |
|---|---|
| DB2 | org.hibernate.dialect.DB2Dialect |
| DB2 AS/400 | org.hibernate.dialect.DB2400Dialect |
| DB2 OS390 | org.hibernate.dialect.DB2390Dialect |
| PostgreSQL | org.hibernate.dialect.PostgreSQLDialect |
| MySQL | org.hibernate.dialect.MySQLDialect |
| MySQL with InnoDB | org.hibernate.dialect.MySQLInnoDBDialect |
| MySQL with MyISAM | org.hibernate.dialect.MySQLMyISAMDialect |
| Oracle(any version) | org.hibernate.dialect.OracleDialect |
| Oracle 9i | org.hibernate.dialect.Oracle9iDialect |
| Oracle 10g | org.hibernate.dialect.Oracle10gDialect |
| Sybase | org.hibernate.dialect.SybaseDialect |
| Sybase Anywhere | org.hibernate.dialect.SybaseAnywhereDialect |
| Microsoft SQL Server | org.hibernate.dialect.SQLServerDialect |
| SAP DB | org.hibernate.dialect.SAPDBDialect |
| Informix | org.hibernate.dialect.InformixDialect |
| HypersonicSQL | org.hibernate.dialect.HSQLDialect |
| Ingres | org.hibernate.dialect.IngresDialect |
| Progress | org.hibernate.dialect.ProgressDialect |
| Mckoi SQL | org.hibernate.dialect.MckoiDialect |
| Interbase | org.hibernate.dialect.InterbaseDialect |
| Pointbase | org.hibernate.dialect.PointbaseDialect |
| FrontBase | org.hibernate.dialect.FrontbaseDialect |
| Firebird | org.hibernate.dialect.FirebirdDialect |
如果你的数据库支持 ANSI、Oracle 或 Sybase 风格的外连接,外连接抓取通常能通过限制往返数据库次数(更多的工作交由数据库自己来完成)来提高效率。外连接抓取允许在单个 SELECT SQL 语句中, 通过 many-to-one、one-to-many、many-to-many 和 one-to-one 关联获取连接对象的整个对象图。
将 hibernate.max_fetch_depth 设为 0 能在全局 范围内禁止外连接抓取。设为 1 或更高值能启用 one-to-one 和 many-to-oneouter 关联的外连接抓取,它们通过 fetch="join" 来映射。
See 第 20.1 节 “抓取策略(Fetching strategies)” for more information.
Oracle 限制那些通过 JDBC 驱动传输的字节数组的数目。如果你希望使用二进值(binary)或 可序列化的(serializable)类型的大对象,你应该开启 hibernate.jdbc.use_streams_for_binary 属性。这是系统级属性。
The properties prefixed by hibernate.cache allow you to use a process or cluster scoped second-level cache system with Hibernate. See the 第 20.2 节 “二级缓存(The Second Level Cache)” for more information.
你可以使用 hibernate.query.substitutions 在 Hibernate 中定义新的查询符号。例如:
hibernate.query.substitutions true=1, false=0
将导致符号 true 和 false 在生成的 SQL 中被翻译成整数常量。
hibernate.query.substitutions toLowercase=LOWER
将允许你重命名 SQL 中的 LOWER 函数。
Hibernate 利用 Simple Logging Facade for Java (SLF4J) 来记录不同系统事件的日志。SLF4J 可以根据你选择的绑定把日志输出到几个日志框架(NOP、Simple、log4j version 1.2、JDK 1.4 logging、JCL 或 logback)上。为了设置日志,你需要在 classpath 里加入 slf4j-api.jar 和你选择的绑定的 JAR 文件(使用 Log4J 时加入 slf4j-log4j12.jar)。更多的细节请参考 SLF4J 文档。要使用 Log4j,你也需要在 classpath 里加入 log4j.properties 文件。Hibernate 里的 src/ 目录里带有一个属性文件的例子。
我们强烈建议你熟悉一下 Hibernate 的日志消息。在不失可读性的前提下,我们做了很多工作,使 Hibernate 的日志可能地详细。这是必要的查错利器。最令人感兴趣的日志分类有如下这些:
表 3.9. Hibernate 日志类别
| 类别 | 功能 |
|---|---|
org.hibernate.SQL | 在所有 SQL DML 语句被执行时为它们记录日志 |
org.hibernate.type | 为所有 JDBC 参数记录日志 |
org.hibernate.tool.hbm2ddl | 在所有 SQL DDL 语句执行时为它们记录日志 |
org.hibernate.pretty | 在 session 清洗(flush)时,为所有与其关联的实体(最多 20 个)的状态记录日志 |
org.hibernate.cache | 为所有二级缓存的活动记录日志 |
org.hibernate.transaction | 为事务相关的活动记录日志 |
org.hibernate.jdbc | 为所有 JDBC 资源的获取记录日志 |
org.hibernate.hql.ast.AST | 在解析查询的时候,记录 HQL 和 SQL 的 AST 分析日志 |
org.hibernate.secure | 为 JAAS 认证请求做日志 |
org.hibernate | 为任何 Hibernate 相关信息记录日志(信息量较大,但对查错非常有帮助) |
在使用 Hibernate 开发应用程序时,你应当总是为 org.hibernate.SQL 开启 debug 级别的日志记录,或者开启 hibernate.show_sql 属性。
org.hibernate.cfg.NamingStrategy 接口允许你为数据库中的对象和 schema 元素指定一个“命名标准”。
你可能会提供一些通过 Java 标识生成数据库标识或将映射定义文件中"逻辑"表/列名处理成"物理"表/列名的规则。这个特性有助于减少冗长的映射定义文件,消除重复内容(如 TBL_ 前缀)。Hibernate 使用的缺省策略是相当精简的。
在加入映射定义前,你可以调用 Configuration.setNamingStrategy() 指定一个不同的命名策略:
SessionFactory sf = new Configuration()
.setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
.addFile("Item.hbm.xml")
.addFile("Bid.hbm.xml")
.buildSessionFactory();
org.hibernate.cfg.ImprovedNamingStrategy 是一个内建的命名策略,对一些应用程序而言,可能是非常有用的起点。
另一个配置方法是在 hibernate.cfg.xml 文件中指定一套完整的配置。这个文件可以当成 hibernate.properties 的替代。若两个文件同时存在,它将覆盖前者的属性。
XML 配置文件被默认是放在 CLASSPATH 的根目录下。下面是一个例子:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- a SessionFactory instance listed as /jndi/name -->
<session-factory
name="java:hibernate/SessionFactory">
<!-- properties -->
<property name="connection.datasource"
>java:/comp/env/jdbc/MyDB</property>
<property name="dialect"
>org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql"
>false</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<property name="jta.UserTransaction"
>java:comp/UserTransaction</property>
<!-- mapping files -->
<mapping resource="org/hibernate/auction/Item.hbm.xml"/>
<mapping resource="org/hibernate/auction/Bid.hbm.xml"/>
<!-- cache settings -->
<class-cache class="org.hibernate.auction.Item" usage="read-write"/>
<class-cache class="org.hibernate.auction.Bid" usage="read-only"/>
<collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/>
</session-factory>
</hibernate-configuration
>
如你所见,这个方法优势在于,在配置文件中指出了映射定义文件的名字。一旦你需要调整 Hibernate 的缓存,hibernate.cfg.xml 也是更方便。注意,使用 hibernate.properties 还是 hibernate.cfg.xml 完全是由你来决定,除了上面提到的 XML 语法的优势之外,两者是等价的。
使用 XML 配置,使得启动 Hibernate 变的异常简单:
SessionFactory sf = new Configuration().configure().buildSessionFactory();
你可以使用如下代码来添加一个不同的 XML 配置文件:
SessionFactory sf = new Configuration()
.configure("catdb.cfg.xml")
.buildSessionFactory();
针对 J2EE 体系,Hibernate 有如下几个集成的方面:
容器管理的数据源(Container-managed datasources): Hibernate 能使用通过容器管理,并由 JNDI 提供的 JDBC 连接。通常,特别是当处理多个数据源的分布式事务的时候,由一个 JTA 兼容的 TransactionManager 和一个 ResourceManager 来处理事务管理(CMT,容器管理的事务)。当然你可以通过 编程方式来划分事务边界(BMT,Bean 管理的事务)。或者为了代码的可移植性,你也也许会想使用可选的 Hibernate Transaction API。
自动 JNDI 绑定:Hibernate 可以在启动后将 SessionFactory 绑定到 JNDI。
JTA Session 绑定: Hibernate Session 可以自动绑定到 JTA 事务作用的范围。只需简单地从 JNDI 查找 SessionFactory 并获得当前的 Session。当 JTA 事务完成时,让 Hibernate来处理 Session 的清洗(flush)与关闭。事务的划分可以是声明式的(CMT),也可以是编程式的(BMT/UserTransaction)。
JMX 部署: 如果你使用支持 JMX 应用程序服务器(如,JBoss AS),那么你可以选择将 Hibernate 部署成托管 MBean。这将为你省去一行从Configuration 构建 SessionFactory 的启动代码。容器将启动你的 HibernateService,并完美地处理好服务间的依赖关系(在 Hibernate 启动前,数据源必须是可用的,等等)。
如果应用程序服务器抛出 "connection containment" 异常,根据你的环境,也许该将配置属性 hibernate.connection.release_mode 设为 after_statement。
在你的架构中,Hibernate 的 Session API 是独立于任何事务分界系统的。如果你让 Hibernate 通过连接池直接使用 JDBC,你需要调用 JDBC API 来打开和关闭你的事务。如果你运行在 J2EE 应用程序服务器中,你也许想用 Bean 管理的事务并在需要的时候调用 JTA API 和 UserTransaction。
为了让你的代码在两种(或其他)环境中可以移植,我们建议使用可选的 Hibernate Transaction API,它包装并隐藏了底层系统。你必须通过设置 Hibernate 配置属性 hibernate.transaction.factory_class 来指定一个 Transaction 实例的工厂类。
有三个标准(内建)的选择:
org.hibernate.transaction.JDBCTransactionFactory委托给数据库(JDBC)事务(默认)
org.hibernate.transaction.JTATransactionFactory如果在上下文环境中存在运行着的事务(如,EJB 会话 Bean 的方法),则委托给容器管理的事务。否则,将启动一个新的事务,并使用 Bean 管理的事务。
org.hibernate.transaction.CMTTransactionFactory委托给容器管理的 JTA 事务
你也可以定义属于你自己的事务策略(如,针对 CORBA 的事务服务)。
Hibernate 的一些特性(比如二级缓存,Contextual Sessions with JTA 等等)需要访问在托管环境中的 JTA TransactionManager。由于 J2EE 没有标准化一个单一的机制,Hibernate 在应用程序服务器中,你必须指定 Hibernate 如何获得 TransactionManager 的引用:
表 3.10. JTA TransactionManagers
| Transaction 工厂类 | 应用程序服务器 |
|---|---|
org.hibernate.transaction.JBossTransactionManagerLookup | JBoss |
org.hibernate.transaction.WeblogicTransactionManagerLookup | Weblogic |
org.hibernate.transaction.WebSphereTransactionManagerLookup | WebSphere |
org.hibernate.transaction.WebSphereExtendedJTATransactionLookup | WebSphere 6 |
org.hibernate.transaction.OrionTransactionManagerLookup | Orion |
org.hibernate.transaction.ResinTransactionManagerLookup | Resin |
org.hibernate.transaction.JOTMTransactionManagerLookup | JOTM |
org.hibernate.transaction.JOnASTransactionManagerLookup | JOnAS |
org.hibernate.transaction.JRun4TransactionManagerLookup | JRun4 |
org.hibernate.transaction.BESTransactionManagerLookup | Borland ES |
与 JNDI 绑定的 Hibernate 的 SessionFactory 能简化工厂的查询,简化创建新的 Session。需要注意的是这与 JNDI 绑定 Datasource 没有关系,它们只是恰巧用了相同的注册表。
如果你希望将 SessionFactory 绑定到一个 JNDI 的名字空间,用属性 hibernate.session_factory_name 指定一个名字(如,java:hibernate/SessionFactory)。如果不设置这个属性,SessionFactory 将不会被绑定到 JNDI 中(在以只读 JNDI 为默认实现的环境中,这个设置尤其有用,如 Tomcat)。
在将 SessionFactory 绑定至 JNDI 时,Hibernate 将使用 hibernate.jndi.url,和 hibernate.jndi.class 的值来实例化初始环境(initial context)。如果它们没有被指定,将使用默认的 InitialContext。
在你调用 cfg.buildSessionFactory()后,Hibernate 会自动将 SessionFactory 注册到 JNDI。这意味这你至少需要在你应用程序的启动代码(或工具类)中完成这个调用,除非你使用 HibernateService 来做 JMX 部署(见后面讨论)。
假若你使用 JNDI SessionFactory,EJB 或者任何其它类都可以从 JNDI 中找到此 SessionFactory。
我们建议,在受管理的环境中,把 SessionFactory 绑定到 JNDI,在其它情况下,使用一个 static(静态的)singleton。为了在你的应用程序代码中隐藏这些细节,我们还建议你用一个 helper 类把实际查找 SessionFactory 的代码隐藏起来,比如 HibernateUtil.getSessionFactory()。注意,这个类也就可以方便地启动 Hibernate,参见第一章。
The easiest way to handle Sessions and transactions is Hibernate's automatic "current" Session management. For a discussion of contextual sessions see 第 2.5 节 “上下文相关的会话(Contextual Session)”. Using the "jta" session context, if there is no Hibernate Session associated with the current JTA transaction, one will be started and associated with that JTA transaction the first time you call sessionFactory.getCurrentSession(). The Sessions retrieved via getCurrentSession() in the "jta" context are set to automatically flush before the transaction completes, close after the transaction completes, and aggressively release JDBC connections after each statement. This allows the Sessions to be managed by the life cycle of the JTA transaction to which it is associated, keeping user code clean of such management concerns. Your code can either use JTA programmatically through UserTransaction, or (recommended for portable code) use the Hibernate Transaction API to set transaction boundaries. If you run in an EJB container, declarative transaction demarcation with CMT is preferred.
为了将 SessionFactory 注册到 JNDI 中,cfg.buildSessionFactory() 这行代码仍需在某处被执行。你可在一个 static 初始化块(像 HibernateUtil 中的那样)中执行它或将 Hibernate 部署为一个托管的服务。
为了部署在一个支持 JMX 的应用程序服务器上,Hibernate 和 org.hibernate.jmx.HibernateService 一同分发,如 Jboss AS。 实际的部署和配置是由应用程序服务器提供者指定的。这里是 JBoss 4.0.x 的 jboss-service.xml 样例:
<?xml version="1.0"?>
<server>
<mbean code="org.hibernate.jmx.HibernateService"
name="jboss.jca:service=HibernateFactory,name=HibernateFactory">
<!-- Required services -->
<depends
>jboss.jca:service=RARDeployer</depends>
<depends
>jboss.jca:service=LocalTxCM,name=HsqlDS</depends>
<!-- Bind the Hibernate service to JNDI -->
<attribute name="JndiName"
>java:/hibernate/SessionFactory</attribute>
<!-- Datasource settings -->
<attribute name="Datasource"
>java:HsqlDS</attribute>
<attribute name="Dialect"
>org.hibernate.dialect.HSQLDialect</attribute>
<!-- Transaction integration -->
<attribute name="TransactionStrategy">
org.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">
org.hibernate.transaction.JBossTransactionManagerLookup</attribute>
<attribute name="FlushBeforeCompletionEnabled"
>true</attribute>
<attribute name="AutoCloseSessionEnabled"
>true</attribute>
<!-- Fetching options -->
<attribute name="MaximumFetchDepth"
>5</attribute>
<!-- Second-level caching -->
<attribute name="SecondLevelCacheEnabled"
>true</attribute>
<attribute name="CacheProviderClass"
>org.hibernate.cache.EhCacheProvider</attribute>
<attribute name="QueryCacheEnabled"
>true</attribute>
<!-- Logging -->
<attribute name="ShowSqlEnabled"
>true</attribute>
<!-- Mapping files -->
<attribute name="MapResources"
>auction/Item.hbm.xml,auction/Category.hbm.xml</attribute>
</mbean>
</server
>
这个文件是部署在 META-INF 目录下的,并会被打包到以 .sar(service archive)为扩展名的 JAR 文件中。同时,你需要将 Hibernate、它所需要的第三方库、你编译好的持久化类以及你的映射定义文件打包进同一个文档。你的企业 Bean(一般为会话 Bean)可能会被打包成它们自己的 JAR 文件,但你也许会将 EJB JAR 文件一同包含进能独立(热)部署的主服务文档。参考 JBoss AS 文档以了解更多的 JMX服务与 EJB 部署的信息。
在应用程序中,用来实现业务问题实体的(如,在电子商务应用程序中的 Customer 和 Order)类就是持久化类。不能认为所有的持久化类的实例都是持久的状态 - 一个实例的状态也可能是瞬时的或脱管的。
如果这些持久化类遵循一些简单的规则,Hibernate 能够工作得更好,这些规则也被称作简单传统 Java 对象(POJO:Plain Old Java Object)编程模型。但是这些规则并不是必需的。 实际上,Hibernate3 对于你的持久化类几乎不做任何设想。你可以用其他的方法来表达领域模型:比如,使用 Map 实例的树型结构。
大多数 Java 程序需要用一个持久化类来表示猫科动物。例如:
package eg;
import java.util.Set;
import java.util.Date;
public class Cat {
private Long id; // identifier
private Date birthdate;
private Color color;
private char sex;
private float weight;
private int litterId;
private Cat mother;
private Set kittens = new HashSet();
private void setId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
void setBirthdate(Date date) {
birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}
void setWeight(float weight) {
this.weight = weight;
}
public float getWeight() {
return weight;
}
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
void setSex(char sex) {
this.sex=sex;
}
public char getSex() {
return sex;
}
void setLitterId(int id) {
this.litterId = id;
}
public int getLitterId() {
return litterId;
}
void setMother(Cat mother) {
this.mother = mother;
}
public Cat getMother() {
return mother;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}
在后续的章节里我们将介绍持久性类的 4 个主要规则的更多细节。
Cat 有一个无参数的构造方法。所有的持久化类都必须有一个默认的构造方法(可以不是 public 的),这样的话 Hibernate 就可以使用 Constructor.newInstance()来实例化它们。 我们强烈建议,在 Hibernate 中,为了运行期代理的生成,构造方法至少是包(package)内可见的。
Cat 有一个属性叫做 id。这个属性映射数据库表的主 键字段。这个属性可以叫任何名字,其类型可以是任何的原始类型、原始类型的包装类型、 java.lang.String 或者是 java.util.Date。(如果你的遗留数据库表有联合主键,你甚至可以用一个用户自定义的类,该类拥有这些类型的属性。参见后面的关于联合标识符的章节。)
标识符属性是可选的。可以不用管它,让 Hibernate 内部来追踪对象的识别。 但是我们并不推荐这样做。
实际上,一些功能只对那些声明了标识符属性的类起作用:
Transitive reattachment for detached objects (cascade update or cascade merge) - see 第 10.11 节 “传播性持久化(transitive persistence)”
Session.saveOrUpdate()
Session.merge()
我们建议你对持久化类声明命名一致的标识属性。我们还建议你使用一个可以为空(也就是说,不是原始类型)的类型。
代理(proxies)是 Hibernate 的一个重要的功能,它依赖的条件是,持久化类或者是非 final 的,或者是实现了一个所有方法都声明为 public 的接口。
你可以用 Hibernate 持久化一个没有实现任何接口的 final 类,但是你不能使用代理来延迟关联加载,这会限制你进行性能优化的选择。
你也应该避免在非 final 类中声明 public final 的方法。如果你想使用一个有 public final 方法的类,你必须通过设置 lazy="false" 来明确地禁用代理。
Cat 为它的所有持久化字段声明了访问方法。很多其他 ORM 工具直接对实例变量进行持久化。我们相信,在关系数据库 schema 和类的内部数据结构之间引入间接层(原文为"非直接",indirection)会好一些。默认情况下 Hibernate 持久化 JavaBeans 风格的属性,认可 getFoo,isFoo 和 setFoo 这种形式的方法名。如果需要,你可以对某些特定属性实行直接字段访问。
属性不需要要声明为 public 的。Hibernate 可以持久化一个有 default、protected 或 private 的 get/set 方法对的属性进行持久化。
子类也必须遵守第一条和第二条规则。它从超类 Cat 继承了标识属性。例如:
package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}
如果你有如下需求,你必须重载 equals() 和 hashCode() 方法:
想把持久类的实例放入 Set 中(当表示多值关联时,推荐这么做),而且
想重用脱管实例
Hibernate 保证,仅在特定会话范围内,持久化标识(数据库的行)和 Java 标识是等价的。因此,一旦我们混合了从不同会话中获取的实例,如果希望 Set 有明确的语义,就必须实现 equals() 和 hashCode()。
实现 equals()/hashCode() 最显而易见的方法是比较两个对象 标识符的值。如果值相同,则两个对象对应于数据库的同一行,因此它们是相等的(如果都被添加到 Set,则在 Set 中只有一个元素)。不幸的是,对生成的标识不能 使用这种方法。Hibernate 仅对那些持久化对象赋标识值,一个新创建的实例将不会有任何标识值。此外, 如果一个实例没有被保存(unsaved),并且它当前正在一个 Set 中,保存它将会给这个对象赋一个标识值。如果 equals() 和 hashCode() 是基于标识值 实现的,则其哈希码将会改变,这违反了 Set 的契约。建议去 Hibernate 的站点阅读关于这个问题的全部讨论。注意,这不是 Hibernate 的问题,而是一般的 Java 对象标识和 Java 对象等价的语义问题。
我们建议使用业务键值相等(Business key equality)来实现 equals() 和 hashCode()。业务键值相等的意思是,equals() 方法仅仅比较形成业务键的属性,它能在现实世界里标识我们的实例(是一个自然的候选码)。
public class Cat {
...
public boolean equals(Object other) {
if (this == other) return true;
if ( !(other instanceof Cat) ) return false;
final Cat cat = (Cat) other;
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode() {
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
}
A business key does not have to be as solid as a database primary key candidate (see 第 12.1.3 节 “关注对象标识(Considering object identity)”). Immutable or unique properties are usually good candidates for a business key.
The following features are currently considered experimental and may change in the near future.
运行期的持久化实体没有必要一定表示为像 POJO 类或 JavaBean 对象那样的形式。Hibernate 也支持动态模型 (在运行期使用 Map 的 Map)和象 DOM4J 的树模型那样的实体表示。使用这种方法,你不用写持久化类,只写映射文件就行了。
By default, Hibernate works in normal POJO mode. You can set a default entity representation mode for a particular SessionFactory using the default_entity_mode configuration option (see 表 3.3 “Hibernate 配置属性”).
下面是用 Map 来表示的例子。首先,在映射文件中,要声明 entity-name 来代替一个类名(或作为一种附属)。
<hibernate-mapping>
<class entity-name="Customer">
<id name="id"
type="long"
column="ID">
<generator class="sequence"/>
</id>
<property name="name"
column="NAME"
type="string"/>
<property name="address"
column="ADDRESS"
type="string"/>
<many-to-one name="organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bag name="orders"
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</class>
</hibernate-mapping
>
注意,虽然是用目标类名来声明关联的,但是关联的目标类型除了是 POJO 之外,也可以是一个动态的实体。
在使用 dynamic-map 为 SessionFactory 设置了默认的实体模式之后,可以在运行期使用 Map 的 Map:
Session s = openSession();
Transaction tx = s.beginTransaction();
// Create a customer
Map david = new HashMap();
david.put("name", "David");
// Create an organization
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Link both
david.put("organization", foobar);
// Save both
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();
动态映射的好处是,变化所需要的时间少了,因为原型不需要实现实体类。然而,你无法进行编译期的类型检查,并可能由此会处理很多的运行期异常。幸亏有了 Hibernate 映射,它使得数据库的 schema 能容易的规格化和合理化,并允许稍后在此之上添加合适的领域模型实现。
实体表示模式也能在每个 Session 的基础上设置:
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Create a customer
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continue on pojoSession
请注意,用 EntityMode 调用 getSession() 是在 Session 的 API 中,而不是 SessionFactory。 这样,新的 Session 共享底层的 JDBC 连接,事务,和其他的上下文信息。这意味着,你不需要在第二个 Session 中调用 flush() 和 close(),同样的,把事务和连接的处理交给原来的工作单元。
More information about the XML representation capabilities can be found in 第 19 章 XML 映射.
org.hibernate.tuple.Tuplizer,以及其子接口,负责根据给定的org.hibernate.EntityMode,来复现片断数据。如果给定的片断数据被认为其是一种数据结构,"tuplizer" 就是一个知道如何创建这样的数据结构,以及如何给这个数据结构赋值的东西。比如说,对于 POJO 这种 Entity Mode,对应的 tuplizer 知道通过其构造方法来创建一个 POJO,再通过其属性访问器来访问 POJO 属性。有两大类高层 Tuplizer,分别是org.hibernate.tuple.entity.EntityTuplizer 和 org.hibernate.tuple.entity.ComponentTuplizer 接口。EntityTuplizer 负责管理上面提到的实体的契约,而 ComponentTuplizer 则是针对组件的。
有两种高层类型的 Tuplizer,分别由 org.hibernate.tuple.entity.EntityTuplizer 和 org.hibernate.tuple.component.ComponentTuplizer 接口代表。EntityTuplizer 负责管理和实体相关的上述合约,而ComponentTuplizer 则负责组件。
用户也可以插入其自定义的 tuplizer。或许您需要一种不同于 dynamic-map entity-mode 中使用的 java.util.HashMap 的 java.util.Map 实现;或许您需要与默认策略不同的代理生成策略(proxy generation strategy)。通过自定义 tuplizer 实现,这两个目标您都可以达到。Tuplizer 定义被附加到它们期望管理的 entity 或者 component 映射中。回到我们的 customer entity 例子:
<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- other properties -->
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl
extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer {
// override the buildInstantiator() method to plug in our custom map...
protected final Instantiator buildInstantiator(
org.hibernate.mapping.PersistentClass mappingInfo) {
return new CustomMapInstantiator( mappingInfo );
}
private static final class CustomMapInstantiator
extends org.hibernate.tuple.DynamicMapInstantitor {
// override the generateMap() method to return our custom map...
protected final Map generateMap() {
return new CustomMap();
}
}
}
org.hibernate.EntityNameResolver 接口是一个解析给定实体实例的实体名称的合约。这个接口定义了一个单一的方法 resolveEntityName,它传递实体实例并预期返回合适的实体名称(null 指明解析器不知道如何解析给定实体实例的实体名称)。一般说来,org.hibernate.EntityNameResolver 在动态模型里最为有用。其中的例子是把代理接口用作你的域模型。Hibernate Test Suite 在 org.hibernate.test.dynamicentity.tuplizer2 下有具有完全相同风格的例子。下面是该包里的一些代码:
/**
* A very trivial JDK Proxy InvocationHandler implementation where we proxy an interface as
* the domain model and simply store persistent state in an internal Map. This is an extremely
* trivial example meant only for illustration.
*/
public final class DataProxyHandler implements InvocationHandler {
private String entityName;
private HashMap data = new HashMap();
public DataProxyHandler(String entityName, Serializable id) {
this.entityName = entityName;
data.put( "Id", id );
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ( methodName.startsWith( "set" ) ) {
String propertyName = methodName.substring( 3 );
data.put( propertyName, args[0] );
}
else if ( methodName.startsWith( "get" ) ) {
String propertyName = methodName.substring( 3 );
return data.get( propertyName );
}
else if ( "toString".equals( methodName ) ) {
return entityName + "#" + data.get( "Id" );
}
else if ( "hashCode".equals( methodName ) ) {
return new Integer( this.hashCode() );
}
return null;
}
public String getEntityName() {
return entityName;
}
public HashMap getData() {
return data;
}
}
/**
*
*/
public class ProxyHelper {
public static String extractEntityName(Object object) {
// Our custom java.lang.reflect.Proxy instances actually bundle
// their appropriate entity name, so we simply extract it from there
// if this represents one of our proxies; otherwise, we return null
if ( Proxy.isProxyClass( object.getClass() ) ) {
InvocationHandler handler = Proxy.getInvocationHandler( object );
if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) {
DataProxyHandler myHandler = ( DataProxyHandler ) handler;
return myHandler.getEntityName();
}
}
return null;
}
// various other utility methods ....
}
/**
* The EntityNameResolver implementation.
* IMPL NOTE : An EntityNameResolver really defines a strategy for how entity names should be
* resolved. Since this particular impl can handle resolution for all of our entities we want to
* take advantage of the fact that SessionFactoryImpl keeps these in a Set so that we only ever
* have one instance registered. Why? Well, when it comes time to resolve an entity name,
* Hibernate must iterate over all the registered resolvers. So keeping that number down
* helps that process be as speedy as possible. Hence the equals and hashCode impls
*/
public class MyEntityNameResolver implements EntityNameResolver {
public static final MyEntityNameResolver INSTANCE = new MyEntityNameResolver();
public String resolveEntityName(Object entity) {
return ProxyHelper.extractEntityName( entity );
}
public boolean equals(Object obj) {
return getClass().equals( obj.getClass() );
}
public int hashCode() {
return getClass().hashCode();
}
}
public class MyEntityTuplizer extends PojoEntityTuplizer {
public MyEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
super( entityMetamodel, mappedEntity );
}
public EntityNameResolver[] getEntityNameResolvers() {
return new EntityNameResolver[] { MyEntityNameResolver.INSTANCE };
}
public String determineConcreteSubclassEntityName(Object entityInstance, SessionFactoryImplementor factory) {
String entityName = ProxyHelper.extractEntityName( entityInstance );
if ( entityName == null ) {
entityName = super.determineConcreteSubclassEntityName( entityInstance, factory );
}
return entityName;
}
...
}
为了注册 org.hibernate.EntityNameResolver,用户必须:
实现自定义的 Tuplizer 并实现 getEntityNameResolvers 方法。
用 registerEntityNameResolver 方法注册到 org.hibernate.impl.SessionFactoryImpl(它是 org.hibernate.SessionFactory 的实现类)。
对象和关系数据库之间的映射通常是用一个 XML 文档来定义的。这个映射文档被设计为易读的,并且可以手工修改。映射语言是以 Java 为中心,这意味着映射文档是按照持久化类的定义来创建的,而非表的定义。
请注意,虽然很多 Hibernate 用户选择手写 XML 映射文档,但也有一些工具可以用来生成映射文档,包括 XDoclet、Middlegen 和 AndroMDA。
下面是一个映射的例子:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat"
table="cats"
discriminator-value="C">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="subclass"
type="character"/>
<property name="weight"/>
<property name="birthdate"
type="date"
not-null="true"
update="false"/>
<property name="color"
type="eg.types.ColorUserType"
not-null="true"
update="false"/>
<property name="sex"
not-null="true"
update="false"/>
<property name="litterId"
column="litterId"
update="false"/>
<many-to-one name="mother"
column="mother_id"
update="false"/>
<set name="kittens"
inverse="true"
order-by="litter_id">
<key column="mother_id"/>
<one-to-many class="Cat"/>
</set>
<subclass name="DomesticCat"
discriminator-value="D">
<property name="name"
type="string"/>
</subclass>
</class>
<class name="Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping
>
我们现在开始讨论映射文档的内容。我们只描述 Hibernate 在运行时用到的文档元素和属性。映射文档还包括一些额外的可选属性和元素,它们在使用 schema 导出工具的时候会影响导出的数据库 schema 结果(比如,not-null 属性)。
所有的 XML 映射都需要定义如上所示的 doctype。DTD 可以从上述 URL 中获取,也可以从 hibernate-x.x.x/src/org/hibernate 目录中、或 hibernate.jar 文件中找到。Hibernate 总是会首先在它的 classptah 中搜索 DTD 文件。如果你发现它是通过连接 Internet 查找 DTD 文件,就对照你的 classpath 目录检查 XML 文件里的 DTD 声明。
Hibernate 首先试图在其 classpath 中解析 DTD。这是依靠在系统中注册的 org.xml.sax.EntityResolver 的一个具体实现,SAXReader 依靠它来读取 xml 文件。这个自定义的 EntityResolver 能辨认两种不同的 systenId 命名空间:
若 resolver 遇到了一个以 http://hibernate.sourceforge.net/ 为开头的 systemId,它会辨认出是 hibernate namespace,resolver 就试图通过加载 Hibernate 类的 classloader 来查找这些实体。
若 resolver 遇到了一个使用 classpath:// URL 协议的 systemId,它会辨认出这是 user namespace,resolver 试图通过(1) 当前线程上下文的 classloader 和(2) 加载 Hibernate class 的 classloader 来查找这些实体。
下面是一个使用用户命名空间(user namespace)的例子:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC '-//Hibernate/Hibernate Mapping DTD 3.0//EN' 'http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd' [
<!ENTITY version "3.5.6-Final">
<!ENTITY today "September 15, 2010">
<!ENTITY types SYSTEM "classpath://your/domain/types.xml">
]>
<hibernate-mapping package="your.domain">
<class name="MyEntity">
<id name="id" type="my-custom-id-type">
...
</id>
<class>
&types;
</hibernate-mapping>
这里的 types.xml 是 your.domain 包中的一个资源,它包含了一个自定义的 typedef。
这个元素包括一些可选的属性。schema 和 catalog 属性, 指明了这个映射所连接(refer)的表所在的 schema 和/或 catalog 名称。假若指定了这个属性,表名会加上所指定的 schema 和 catalog 的名字扩展为全限定名。假若没有指定,表名就不会使用全限定名。default-cascade 指定了未明确注明 cascade 属性的 Java 属性和 集合类 Hibernate 会采取什么样的默认级联风格。auto-import 属性默认让我们在查询语言中可以使用非全限定名的类名。
<hibernate-mapping
schem
a="schemaName"
catal
og="catalogName"
defau
lt-cascade="cascade_style"
defau
lt-access="field|property|ClassName"
defau
lt-lazy="true|false"
auto-
import="true|false"
packa
ge="package.name"
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
假若你有两个持久化类,它们的非全限定名是一样的(就是两个类的名字一样,所在的包不一样 — 译者注),你应该设置 auto-import="false"。如果你把一个“导入过”的名字同时对应两个类,Hibernate 会抛出一个异常。
注意 hibernate-mapping 元素允许你嵌套多个如上所示的 <class> 映射。但是最好的做法(也许一些工具需要的)是一个持久化类(或一个类的继承层次)对应一个映射文件,并以持久化的超类名称命名,例如:Cat.hbm.xml、Dog.hbm.xml,或者如果使用继承,Animal.hbm.xml。
你可以使用 class 元素来定义一个持久化类。例如:
<class
name="
ClassName"
table=
"tableName"
discri
minator-value="discriminator_value"
mutabl
e="true|false"
schema
="owner"
catalo
g="catalog"
proxy=
"ProxyInterface"
dynami
c-update="true|false"
dynami
c-insert="true|false"
select
-before-update="true|false"
polymo
rphism="implicit|explicit"
where=
"arbitrary sql where condition"
persis
ter="PersisterClass"
batch-
size="N"
optimi
stic-lock="none|version|dirty|all"
lazy="(16)true|false"
entity(17)-name="EntityName"
check=(18)"arbitrary sql check condition"
rowid=(19)"rowid"
subsel(20)ect="SQL expression"
abstra(21)ct="true|false"
node="element-name"
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(16) |
|
(17) |
|
(18) |
|
(19) |
|
(20) |
|
(21) |
|
若指明的持久化类实际上是一个接口,这也是完全可以接受的。之后你可以用元素 <subclass> 来指定该接口的实际实现类。你可以持久化任何 static(静态的)内部类。你应该使用标准的类名格式来指定类名,比如:Foo$Bar。
不可变类,mutable="false" 不可以被应用程序更新或者删除。这允许 Hibernate 实现一些小小的性能优化。
可选的 proxy 属性允许延迟加载类的持久化实例。Hibernate 开始会返回实现了这个命名接口的 CGLIB 代理。当代理的某个方法被实际调用的时候,真实的持久化对象才会被装载。参见下面的“用于延迟装载的代理”。
Implicit(隐式)的多态是指,如果查询时给出的是任何超类、该类实现的接口或者该类的名字,都会返回这个类的实例;如果查询中给出的是子类的名字,则会返回子类的实例。Explicit(显式)的多态是指,只有在查询时给出明确的该类名字时才会返回这个类的实例; 同时只有在这个 <class> 的定义中作为 <subclass> 或者 <joined-subclass> 出现的子类,才会可能返回。在大多数情况下,默认的 polymorphism="implicit" 都是合适的。显式的多态在有两个不同的类映射到同一个表的时候很有用。(允许一个“轻量级”的类,只包含部分表字段)。
persister 属性可以让你定制这个类使用的持久化策略。你可以指定你自己实现 org.hibernate.persister.EntityPersister 的子类,你甚至可以完全从头开始编写一个 org.hibernate.persister.ClassPersister 接口的实现,比如是用储存过程调用、序列化到文件或者 LDAP 数据库来实现。参阅 org.hibernate.test.CustomPersister,这是持久化到 Hashtable 的一个简单例子。
请注意 dynamic-update 和 dynamic-insert 的设置并不会继承到子类,所以在 <subclass> 或者 <joined-subclass> 元素中可能需要再次设置。这些设置在某些情况下能够提高效率,而其他情况下则反而可能降低性能。
使用 select-before-update 通常会降低性能。如果你重新连接一个脱管(detached)对象实例 到一个 Session 中时,它可以防止数据库不必要的触发 update。这就很有用了。
如果你打开了dynamic-update,你可以选择几种乐观锁定的策略:
version(版本检查):检查 version/timestamp 字段
all(全部):检查全部字段
dirty(脏检查):只检察修改过的字段,允许某些并行更新
none(不检查):不使用乐观锁定
我们强烈建议你在 Hibernate 中使用 version/timestamp 字段来进行乐观锁定。这个选择可以优化性能,且能够处理对脱管实例的修改(例如:在使用 Session.merge() 的时候)。
对 Hibernate 映射来说视图和表是没有区别的,这是因为它们在数据层都是透明的( 注意:一些数据库不支持视图属性,特别是更新的时候)。有时你想使用视图,但却不能在数据库中创建它(例如:在遗留的 schema 中)。这样的话,你可以映射一个不可变的(immutable)并且是 只读的实体到一个给定的 SQL 子查询表达式:
<class name="Summary">
<subselect>
select item.name, max(bid.amount), count(*)
from item
join bid on bid.item_id = item.id
group by item.name
</subselect>
<synchronize table="item"/>
<synchronize table="bid"/>
<id name="name"/>
...
</class
>
定义这个实体用到的表为同步(synchronize),确保自动刷新(auto-flush)正确执行,并且依赖原实体的查询不会返回过期数据。在属性元素和嵌套映射元素中都可使用 <subselect>。
被映射的类必须定义对应数据库表主键字段。大多数类有一个 JavaBeans 风格的属性, 为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。
<id
name="
propertyName"
type="
typename"
column
="column_name"
unsave
d-value="null|any|none|undefined|id_value"
access
="field|property|ClassName">
node="element-name|@attribute-name|element/@attribute|."
<generator class="generatorClass"/>
</id
>
|
|
|
|
|
|
|
|
|
|
如果 name 属性不存在,会认为这个类没有标识属性。
unsaved-value 属性在 Hibernate3 中几乎不再需要。
还有一个另外的 <composite-id> 定义可以访问旧式的多主键数据。我们非常不鼓励使用这种方式。
可选的 <generator> 子元素是一个 Java 类的名字,用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数,用 <param> 元素来传递。
<id name="id" type="long" column="cat_id">
<generator class="org.hibernate.id.TableHiLoGenerator">
<param name="table"
>uid_table</param>
<param name="column"
>next_hi_value_column</param>
</generator>
</id
>
所有的生成器都实现 org.hibernate.id.IdentifierGenerator 接口。这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然,Hibernate 提供了很多内置的实现。下面是一些内置生成器的快捷名字:
increment用于为 long, short 或者 int 类型生成 唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。在集群下不要使用。
identity对 DB2,MySQL,MS SQL Server,Sybase 和 HypersonicSQL 的内置标识字段提供支持。返回的标识符是 long,short 或者 int 类型的。
sequence在 DB2,PostgreSQL,Oracle,SAP DB,McKoi 中使用序列(sequence), 而在 Interbase 中使用生成器(generator)。返回的标识符是 long,short 或者 int 类型的。
hilo使用一个高/低位算法高效的生成 long,short 或者 int 类型的标识符。给定一个表和字段(默认分别是 hibernate_unique_key 和 next_hi)作为高位值的来源。高/低位算法生成的标识符只在一个特定的数据库中是唯一的。
seqhilo使用一个高/低位算法来高效的生成 long,short 或者 int 类型的标识符,给定一个数据库序列(sequence)的名字。
uuid用一个 128-bit 的 UUID 算法生成字符串类型的标识符,这在一个网络中是唯一的(使用了 IP 地址)。UUID 被编码为一个 32 位 16 进制数字的字符串。
guid在 MS SQL Server 和 MySQL 中使用数据库生成的 GUID 字符串。
native根据底层数据库的能力选择 identity、sequence 或者 hilo 中的一个。
assigned让应用程序在调用 save() 之前为对象分配一个标识符。这是 <generator> 元素没有指定时的默认生成策略。
select通过数据库触发器选择一些唯一主键的行并返回主键值来分配一个主键。
foreign使用另外一个相关联的对象的标识符。它通常和 <one-to-one> 联合起来使用。
sequence-identity一种特别的序列生成策略,它使用数据库序列来生成实际值,但将它和 JDBC3 的 getGeneratedKeys 结合在一起,使得在插入语句执行的时候就返回生成的值。目前为止只有面向 JDK 1.4 的 Oracle 10g 驱动支持这一策略。由于 Oracle 驱动程序的一个 bug,这些插入语句的注释被关闭了。
hilo 和 seqhilo 生成器给出了两种 hi/lo 算法的实现, 这是一种很令人满意的标识符生成算法。第一种实现需要一个“特殊”的数据库表来保存下一个可用的“hi”值。 第二种实现使用一个 Oracle 风格的序列(在被支持的情况下)。
<id name="id" type="long" column="cat_id">
<generator class="hilo">
<param name="table"
>hi_value</param>
<param name="column"
>next_value</param>
<param name="max_lo"
>100</param>
</generator>
</id
>
<id name="id" type="long" column="cat_id">
<generator class="seqhilo">
<param name="sequence"
>hi_value</param>
<param name="max_lo"
>100</param>
</generator>
</id
>
可惜的是,你在为 Hibernate 自行提供 Connection 时无法使用 hilo。 当 Hibernate 使用 JTA 获取应用服务器的数据源连接时,你必须正确地配置 hibernate.transaction.manager_lookup_class。
UUID 包含:IP 地址、JVM 的启动时间(精确到 1/4 秒)、系统时间和一个计数器值(在 JVM 中唯一)。 在 Java 代码中不可能获得 MAC 地址或者内存地址,所以这已经是我们在不使用 JNI 的前提下的能做的最好实现了。
对于内部支持标识字段的数据库(DB2、MySQL、Sybase 和 MS SQL),你可以使用 identity 关键字生成。对于内部支持序列的数据库(DB2、Oracle、PostgreSQL、Interbase、McKoi 和 SAP DB),你可以使用 sequence 风格的关键字生成。这两种方式对于插入一个新的对象都需要两次 SQL 查询。例如:
<id name="id" type="long" column="person_id">
<generator class="sequence">
<param name="sequence"
>person_id_sequence</param>
</generator>
</id
>
<id name="id" type="long" column="person_id" unsaved-value="0">
<generator class="identity"/>
</id
>
对于跨平台开发,native 策略会从 identity、sequence 和 hilo 中进行选择,选择哪一个,这取决于底层数据库的支持能力。
如果你需要应用程序分配一个标示符(而非 Hibernate 来生成),你可以使用 assigned 生成器。这种特殊的生成器会使用已经分配给对象的标识符属性的标识符值。 这个生成器使用一个自然键(natural key,有商业意义的列-译注)作为主键,而不是使用一个代理键( surrogate key,没有商业意义的列-译注)。这是没有指定 <generator> 元素时的默认行为。
当选择 assigned 生成器时,除非有一个 version 或 timestamp 属性,或者你定义了 Interceptor.isUnsaved(),否则需要让 Hiberante 使用 unsaved-value="undefined",强制 Hibernatet 查询数据库来确定一个实例是瞬时的(transient) 还是脱管的(detached)。
仅仅用于遗留的 schema 中(Hibernate 不能用触发器生成 DDL)。
<id name="id" type="long" column="person_id">
<generator class="select">
<param name="key"
>socialSecurityNumber</param>
</generator>
</id
>
在上面的例子中,类定义了一个命名为 socialSecurityNumber 的具有唯一值的属性,它是一个自然键(natural key),命名为 person_id 的代理键(surrogate key)的值由触发器生成。
从 3.2.3 版本开始,有两个代表不同标识符生成概念的新的生成器。第一个概念是数据库移植性;第二个是优化。优化表示你不需对每个新标识符的请求都查询数据库。从 3.3.x 开始,这两个新的生成器都是用来取代上面所述的生成器的。然而,它们也包括在当前版本里且可以由 FQN 进行引用。
这些生成器的第一个是 org.hibernate.id.enhanced.SequenceStyleGenerator,首先,它是作为 sequence 生成器的替代物,其次,它是比 native 具有更好移植性的生成器。这是因为 native 通常在 identity 和 sequence 之间选择,它有差别很大的 semantic,在移植时会导致潜在的问题。然而,org.hibernate.id.enhanced.SequenceStyleGenerator 以不同的方式实现移植性。它根据所使用的方言的能力,在数据库表或序列之间选择以存储其增量。这和 native 的区别是基于表或序列的存储具有恰好相同的 semantic。实际上,序列就是 Hibernate 试图用基于表的生成器来模拟的。这个生成器有如下的配置参数:
sequence_name(可选 — 默认为 hibernate_sequence):序列或表的名字
initial_value(可选,默认为 1):从序列/表里获取的初始值。按照序列创建的术语,这等同于子句 "STARTS WITH"。
increment_size(可选 - 缺省为 1):对序列/表的调用应该区分的值。按照序列创建的术语,这等同于子句 "INCREMENT BY"。
force_table_use(可选 - 缺省为 false):即使方言可能支持序列,是否也应该强制把表用作后台结构。
value_column(可选 - 缺省为 next_val):只和表结构相关,它是用于保存值的字段的名称。
optimizer (optional - defaults to none): See 第 5.1.6 节 “标识符生成器的优化”
新生成器的第二个是 org.hibernate.id.enhanced.TableGenerator,它的目的首先是替代 table 生成器,即使它实际上比 org.hibernate.id.MultipleHiLoPerTableGenerator 功能要强得多;其次,作为利用可插拔 optimizer 的 org.hibernate.id.MultipleHiLoPerTableGenerator 的替代品。基本上这个生成器定义了一个可以利用多个不同的键值记录存储大量不同增量值的表。这个生成器有如下的配置参数:
table_name(可选 — 默认是 hibernate_sequences):所用的表的名称。
value_column_name(可选 — 默认为 next_val):用于存储这些值的表的字段的名字。
segment_column_name(可选,默认为 sequence_name):用于保存 "segment key" 的字段的名称。这是标识使用哪个增量值的值。
segment_value(可选,默认为 default):我们为这个生成器获取增量值的 segment 的 "segment key"。
segment_value_length(可选 — 默认为 255):用于 schema 生成;创建 Segment Key 字段的字段大小。
initial_value(可选 — 默认是 1):从表里获取的初始值。
increment_size(可选 — 默认是 1):对表随后的调用应该区分的值。
optimizer (optional - defaults to ): See 第 5.1.6 节 “标识符生成器的优化”
For identifier generators that store values in the database, it is inefficient for them to hit the database on each and every call to generate a new identifier value. Instead, you can group a bunch of them in memory and only hit the database when you have exhausted your in-memory value group. This is the role of the pluggable optimizers. Currently only the two enhanced generators (第 5.1.5 节 “增强的标识符生成器” support this operation.
none(如果没有指定 optimizer,通常这是缺省配置):这不会执行任何优化,在每次请求时都访问数据库。
hilo:对从数据库获取的值应用 hi/lo 算法。用于这个 optimizer 的从数据库获取的值应该是有序的。它们表明“组编号”。increment_size 将乘以内存里的值来定义组的“hi 值”。
pooled:和 hilo 一样,这个 optimizer 试图最小化对数据库的访问。然而,我们只是简单地把“下一组”的起始值而不是把序列值和分组算法的组合存入到数据库结构里。在这里,increment_size 表示数据库里的值。
<composite-id
name="propertyName"
class="ClassName"
mapped="true|false"
access="field|property|ClassName">
node="element-name|."
<key-property name="propertyName" type="typename" column="column_name"/>
<key-many-to-one name="propertyName" class="ClassName" column="column_name"/>
......
</composite-id
>
如果表使用联合主键,你可以映射类的多个属性为标识符属性。<composite-id> 元素接受 <key-property> 属性映射和 <key-many-to-one> 属性映射作为子元素。
<composite-id>
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id
>
你的持久化类必须覆盖 equals() 和 hashCode() 方法,来实现组合的标识符的相等判断。实现 Serializable 接口也是必须的。
不幸的是,这种组合关键字的方法意味着一个持久化类是它自己的标识。除了对象自己之外,没有什么方便的“把手”可用。你必须初始化持久化类的实例,填充它的标识符属性,再 load() 组合关键字关联的持久状态。我们把这种方法称为 embedded(嵌入式)的组合标识符,在重要的应用中不鼓励使用这种用法。
第二种方法我们称为 mapped(映射式)组合标识符(mapped composite identifier),<composite-id> 元素中列出的标识属性不但在持久化类出现,还形成一个独立的标识符类。
<composite-id class="MedicareId" mapped="true">
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id
>
在这个例子中,组合标识符类 MedicareId 和实体类都含有 medicareNumber 和 dependent 属性。标识符类必须重载 equals() 和 hashCode() 并且实现 Serializable 接口。这种方法的缺点是出现了明显的代码重复。
下面列出的属性是用来指定一个映射式组合标识符的:
mapped(可选,默认为 false):指明使用一个映射式组合标识符,其包含的属性映射同时在实体类和组合标识符类中出现。
class(可选,但对映射式组合标识符必须指定):作为组合标识符类使用的类名。
We will describe a third, even more convenient approach, where the composite identifier is implemented as a component class in 第 8.4 节 “组件作为联合标识符(Components as composite identifiers)”. The attributes described below apply only to this alternative approach:
name(可选,但对这种方法而言必须):包含此组件标识符的组件类型的名字(参阅第 9 章)。
access(可选 — 默认为 property):Hibernate 用来访问属性值的策略。
class(可选 — 默认会用反射来自动判定属性类型 ):用来作为组合标识符的组件类的类名(参阅下一节)。
第三种方式,被称为 identifier component(标识符组件)是我们对几乎所有应用都推荐使用的方式。
在"一棵对象继承树对应一个表"的策略中,<discriminator> 元素是必需的,它定义了表的鉴别器字段。鉴别器字段包含标志值,用于告知持久化层应该为某个特定的行创建哪一个子类的实例。如下这些受到限制的类型可以使用:string、character、integer、byte、short、boolean、yes_no、true_false。
<discriminator
column
="discriminator_column"
type="
discriminator_type"
force=
"true|false"
insert
="true|false"
formul
a="arbitrary sql expression"
/>
|
|
|
|
|
|
|
|
|
|
鉴别器字段的实际值是根据 <class> 和 <subclass> 元素中的 discriminator-value 属性得来的。
force 属性仅仅在这种情况下有用的:表中包含没有被映射到持久化类的附加辨别器值。这种情况不会经常遇到。
使用 formula 属性你可以定义一个 SQL 表达式,用来判断一行数据的类型。
<discriminator
formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end"
type="integer"/>
<version> 元素是可选的,表明表中包含附带版本信息的数据。这在你准备使用 长事务(long transactions)的时候特别有用。下面是更多信息:
<version
column
="version_column"
name="
propertyName"
type="
typename"
access
="field|property|ClassName"
unsave
d-value="null|negative|undefined"
genera
ted="never|always"
insert
="true|false"
node="element-name|@attribute-name|element/@attribute|."
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
版本号必须是以下类型:long、integer、short、timestamp 或者 calendar。
一个脱管(detached)实例的 version 或 timestamp 属性不能为空(null),因为 Hibernate 不管 unsaved-value 被指定为何种策略,它将任何属性为空的 version 或 timestamp 实例看作为瞬时(transient)实例。 避免 Hibernate 中的传递重附(transitive reattachment)问题的一个简单方法是 定义一个不能为空的 version 或 timestamp 属性,特别是在人们使用程序分配的标识符(assigned identifiers) 或复合主键时非常有用。
可选的 <timestamp> 元素指明了表中包含时间戳数据。这用来作为版本的替代。时间戳本质上是一种对乐观锁定的一种不是特别安全的实现。当然,有时候应用程序可能在其他方面使用时间戳。
<timestamp
column
="timestamp_column"
name="
propertyName"
access
="field|property|ClassName"
unsave
d-value="null|undefined"
source
="vm|db"
genera
ted="never|always"
node="element-name|@attribute-name|element/@attribute|."
/>
|
|
|
|
|
|
|
|
|
|
|
|
注意,<timestamp> 和 <version type="timestamp"> 是等价的。并且 <timestamp source="db"> 和 <version type="dbtimestamp"> 是等价的。
<property> 元素为类定义了一个持久化的、JavaBean 风格的属性。
<property
name="
propertyName"
column
="column_name"
type="
typename"
update
="true|false"
insert
="true|false"
formul
a="arbitrary SQL expression"
access
="field|property|ClassName"
lazy="
true|false"
unique
="true|false"
not-nu
ll="true|false"
optimi
stic-lock="true|false"
genera
ted="never|insert|always"
node="element-name|@attribute-name|element/@attribute|."
index="index_name"
unique_key="unique_key_id"
length="L"
precision="P"
scale="S"
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typename 可以是如下几种:
Hibernate 基本类型名(比如:integer, string, character,date, timestamp, float, binary, serializable, object, blob)。
一个 Java 类的名字,这个类属于一种默认基础类型(比如:int, float,char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。
一个可以序列化的 Java 类的名字。
一个自定义类型的类的名字。(比如:com.illflow.type.MyCustomType)。
如果你没有指定类型,Hibernarte 会使用反射来得到这个名字的属性,以此来猜测正确的 Hibernate 类型。Hibernate 会按照规则 2,3,4 的顺序对属性读取器(getter方法)的返回类进行解释。然而,这还不够。 在某些情况下你仍然需要 type 属性。(比如,为了区别Hibernate.DATE 和Hibernate.TIMESTAMP,或者为了指定一个自定义类型。)
access 属性用来让你控制 Hibernate 如何在运行时访问属性。在默认情况下,Hibernate 会使用属性的 get/set 方法对(pair)。如果你指明 access="field",Hibernate 会忽略 get/set 方法对,直接使用反射来访问成员变量。你也可以指定你自己的策略,这就需要你自己实现 org.hibernate.property.PropertyAccessor 接口,再在 access 中设置你自定义策略类的名字。
衍生属性(derive propertie)是一个特别强大的特征。这些属性应该定义为只读,属性值在装载时计算生成。 你用一个 SQL 表达式生成计算的结果,它会在这个实例转载时翻译成一个 SQL 查询的 SELECT 子查询语句。
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>
注意,你可以使用实体自己的表,而不用为这个特别的列定义别名(上面例子中的 customerId)。同时注意,如果你不喜欢使用属性, 你可以使用嵌套的 <formula> 映射元素。
通过 many-to-one 元素,可以定义一种常见的与另一个持久化类的关联。这种关系模型是多对一关联(实际上是一个对象引用-译注):这个表的一个外键引用目标表的主键字段。
<many-to-one
name="
propertyName"
column
="column_name"
class=
"ClassName"
cascad
e="cascade_style"
fetch=
"join|select"
update
="true|false"
insert
="true|false"
proper
ty-ref="propertyNameFromAssociatedClass"
access
="field|property|ClassName"
unique
="true|false"
not-nu
ll="true|false"
optimi
stic-lock="true|false"
lazy="
proxy|no-proxy|false"
not-fo
und="ignore|exception"
entity
-name="EntityName"
formul
a="arbitrary SQL expression"
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Setting a value of the cascade attribute to any meaningful value other than none will propagate certain operations to the associated object. The meaningful values are divided into three categories. First, basic operations, which include: persist, merge, delete, save-update, evict, replicate, lock and refresh; second, special values: delete-orphan; and third, all comma-separated combinations of operation names: cascade="persist,merge,evict" or cascade="all,delete-orphan". See 第 10.11 节 “传播性持久化(transitive persistence)” for a full explanation. Note that single valued, many-to-one and one-to-one, associations do not support orphan delete.
一个典型的简单 many-to-one 定义例子:
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
property-ref 属性只应该用来对付遗留下来的数据库系统,可能有外键指向对方关联表的是个非主键字段(但是应该是一个惟一关键字)的情况下。这是一种十分丑陋的关系模型。比如说,假设 Product 类有一个惟一的序列号,它并不是主键。(unique 属性控制 Hibernate 通过 SchemaExport 工具进行的 DDL 生成。)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
那么关于 OrderItem 的映射可能是:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
当然,我们决不鼓励这种用法。
如果被引用的唯一主键由关联实体的多个属性组成,你应该在名称为 <properties> 的元素 里面映射所有关联的属性。
假若被引用的唯一主键是组件的属性,你可以指定属性路径:
<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>
持久化对象之间一对一的关联关系是通过 one-to-one 元素定义的。
<one-to-one
name="
propertyName"
class=
"ClassName"
cascad
e="cascade_style"
constr
ained="true|false"
fetch=
"join|select"
proper
ty-ref="propertyNameFromAssociatedClass"
access
="field|property|ClassName"
formul
a="any SQL expression"
lazy="
proxy|no-proxy|false"
entity
-name="EntityName"
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
foreign-key="foreign_key_name"
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
有两种不同的一对一关联:
主键关联
惟一外键关联
主键关联不需要额外的表字段;如果两行是通过这种一对一关系相关联的,那么这两行就共享同样的主关键字值。所以如果你希望两个对象通过主键一对一关联,你必须确认它们被赋予同样的标识值。
比如说,对下面的 Employee 和 Person 进行主键一对一关联:
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
现在我们必须确保 PERSON 和 EMPLOYEE 中相关的字段是相等的。我们使用一个被成为 foreign 的特殊的 hibernate 标识符生成策略:
<class name="person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="foreign">
<param name="property"
>employee</param>
</generator>
</id>
...
<one-to-one name="employee"
class="Employee"
constrained="true"/>
</class
>
一个刚刚保存的 Person 实例被赋予和该 Person 的 employee 属性所指向的 Employee 实例同样的关键字值。
另一种方式是一个外键和一个惟一关键字对应,上面的 Employee 和 Person 的例子,如果使用这种关联方式,可以表达成:
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
如果在 Person 的映射加入下面几句,这种关联就是双向的:
<one-to-one name="employee" class="Employee" property-ref="person"/>
<natural-id mutable="true|false"/>
<property ... />
<many-to-one ... />
......
</natural-id
>
我们建议使用代用键(键值不具备实际意义)作为主键,我们仍然应该尝试为所有的实体采用自然的键值作为(附加——译者注)标示。自然键(natural key)是单个或组合属性,他们必须唯一且非空。如果它还是不可变的那就更理想了。在 <natural-id> 元素中列出自然键的属性。Hibernate 会帮你生成必须的唯一键值和非空约束,你的映射会更加的明显易懂(原文是 self-documenting,自我注解)。
我们强烈建议你实现 equals() 和 hashCode() 方法,来比较实体的自然键属性。
这一映射不是为了把自然键作为主键而准备的。
mutable(可选,默认为 false):默认情况下,自然标识属性被假定为不可变的(常量)。
<component> 元素把子对象的一些元素与父类对应的表的一些字段映射起来。然后组件可以定义它们自己的属性、组件或者集合。参见后面的“Components”一章。
<component
name="
propertyName"
class=
"className"
insert
="true|false"
update
="true|false"
access
="field|property|ClassName"
lazy="
true|false"
optimi
stic-lock="true|false"
unique
="true|false"
node="element-name|."
>
<property ...../>
<many-to-one .... />
........
</component
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
其 <property> 子标签为子类的一些属性与表字段之间建立映射。
<component> 元素允许加入一个 <parent> 子元素,在组件类内部就可以有一个指向其容器的实体的反向引用。
The <dynamic-component> element allows a Map to be mapped as a component, where the property names refer to keys of the map. See 第 8.5 节 “动态组件(Dynamic components)” for more information.
<properties> 元素允许定义一个命名的逻辑分组(grouping)包含一个类中的多个属性。这个元素最重要的用处是允许多个属性的组合作为 property-ref 的目标(target)。这也是定义多字段唯一约束的一种方便途径。例如:
<properties
name="
logicalName"
insert
="true|false"
update
="true|false"
optimi
stic-lock="true|false"
unique
="true|false"
>
<property ...../>
<many-to-one .... />
........
</properties
>
|
|
|
|
|
|
|
|
|
|
例如,如果我们有如下的 <properties> 映射:
<class name="Person">
<id name="personNumber"/>
...
<properties name="name"
unique="true" update="false">
<property name="firstName"/>
<property name="initial"/>
<property name="lastName"/>
</properties>
</class
>
然后,我们可能有一些遗留的数据关联,引用 Person 表的这个唯一键,而不是主键:
<many-to-one name="person"
class="Person" property-ref="name">
<column name="firstName"/>
<column name="initial"/>
<column name="lastName"/>
</many-to-one
>
我们并不推荐这样使用,除非在映射遗留数据的情况下。
最后,多态持久化需要为父类的每个子类都进行定义。对于“每一棵类继承树对应一个表”的策略来说,就需要使用 <subclass> 定义。
<subclass
name="
ClassName"
discri
minator-value="discriminator_value"
proxy=
"ProxyInterface"
lazy="
true|false"
dynamic-update="true|false"
dynamic-insert="true|false"
entity-name="EntityName"
node="element-name"
extends="SuperclassName">
<property .... />
.....
</subclass
>
|
|
|
|
|
|
|
|
每个子类都应该定义它自己的持久化属性和子类。<version> 和 <id> 属性可以从根父类继承下来。在一棵继承树上的每个子类都必须定义一个唯一的 discriminator-value。如果没有指定,就会使用 Java 类的全限定名。
For information about inheritance mappings see 第 9 章 继承映射(Inheritance Mapping) .
此外,每个子类可能被映射到他自己的表中(每个子类一个表的策略)。被继承的状态通过和超类的表关联得到。我们使用 <joined-subclass> 元素。
<joined-subclass
name="
ClassName"
table=
"tablename"
proxy=
"ProxyInterface"
lazy="
true|false"
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<key .... >
<property .... />
.....
</joined-subclass
>
|
|
|
|
|
|
|
|
这种映射策略不需要指定辨别标志(discriminator)字段。但是,每一个子类都必须使用 <key> 元素指定一个表字段来持有对象的标识符。本章开始的映射可以被用如下方式重写:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="eg">
<class name="Cat" table="CATS">
<id name="id" column="uid" type="long">
<generator class="hilo"/>
</id>
<property name="birthdate" type="date"/>
<property name="color" not-null="true"/>
<property name="sex" not-null="true"/>
<property name="weight"/>
<many-to-one name="mate"/>
<set name="kittens">
<key column="MOTHER"/>
<one-to-many class="Cat"/>
</set>
<joined-subclass name="DomesticCat" table="DOMESTIC_CATS">
<key column="CAT"/>
<property name="name" type="string"/>
</joined-subclass>
</class>
<class name="eg.Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping
>
For information about inheritance mappings see 第 9 章 继承映射(Inheritance Mapping) .
第三种选择是仅仅映射类继承树中具体类部分到表中(每个具体类一张表的策略)。其中,每张表定义了类的所有持久化状态,包括继承的状态。在 Hibernate 中,并不需要完全显式地映射这样的继承树。你可以简单地使用单独的 <class> 定义映射每个类。然而,如果你想使用多态关联(例如,一个对类继承树中超类的关联),你需要使用 <union-subclass> 映射。
<union-subclass
name="
ClassName"
table=
"tablename"
proxy=
"ProxyInterface"
lazy="
true|false"
dynamic-update="true|false"
dynamic-insert="true|false"
schema="schema"
catalog="catalog"
extends="SuperclassName"
abstract="true|false"
persister="ClassName"
subselect="SQL expression"
entity-name="EntityName"
node="element-name">
<property .... />
.....
</union-subclass
>
|
|
|
|
|
|
|
|
这种映射策略不需要指定辨别标志(discriminator)字段。
For information about inheritance mappings see 第 9 章 继承映射(Inheritance Mapping) .
使用 <join> 元素,假若在表之间存在一对一关联,可以将一个类的属性映射到多张表中。
<join
table=
"tablename"
schema
="owner"
catalo
g="catalog"
fetch=
"join|select"
invers
e="true|false"
option
al="true|false">
<key ... />
<property ... />
...
</join
>
|
|
|
|
|
|
|
|
|
|
|
|
例如,一个人(person)的地址(address)信息可以被映射到单独的表中(并保留所有属性的值类型语义):
<class name="Person"
table="PERSON">
<id name="id" column="PERSON_ID"
>...</id>
<join table="ADDRESS">
<key column="ADDRESS_ID"/>
<property name="address"/>
<property name="zip"/>
<property name="country"/>
</join>
...
此特性常常对遗留数据模型有用,我们推荐表个数比类个数少,以及细粒度的领域模型。然而,在单独的继承树上切换继承映射策略是有用的,后面会解释这点。
我们目前已经见到过 <key> 元素多次了。这个元素在父映射元素定义了对新表的连接,并且在被连接表中定义了一个外键引用原表的主键的情况下经常使用。
<key
column
="columnname"
on-del
ete="noaction|cascade"
proper
ty-ref="propertyName"
not-nu
ll="true|false"
update
="true|false"
unique
="true|false"
/>
|
|
|
|
|
|
|
|
|
|
|
|
对那些看重删除性能的系统,我们推荐所有的键都应该定义为 on-delete="cascade",这样 Hibernate 将使用数据库级的 ON CASCADE DELETE 约束,而不是多个 DELETE 语句。注意,这个特性会绕过 Hibernate 通常对版本数据(versioned data)采用的乐观锁策略。
not-null 和 update 属性在映射单向一对多关联的时候有用。如果你映射一个单向一对多关联到非空的(non-nullable)外键,你必须 用 <key not-null="true"> 定义此键字段。
任何接受 column 属性的映射元素都可以选择接受 <column> 子元素。同样的,formula 子元素也可以替换 <formula> 属性。
<column
name="column_name"
length="N"
precision="N"
scale="N"
not-null="true|false"
unique="true|false"
unique-key="multicolumn_unique_key_name"
index="index_name"
sql-type="sql_type_name"
check="SQL expression"
default="SQL expression"
read="SQL expression"
write="SQL expression"/>
<formula
>SQL expression</formula
>
column 上的大多数属性都提供了在自动模式生成过程中对 DDL 进行裁剪的方法。read 和 write 属性允许你指定 Hibernate 用于访问字段值的自定义的 SQL。关于更多的内容,请参考 column read and write expressions。
column 和 formula 属性甚至可以在同一个属性或关联映射中被合并来表达,例如,一些奇异的连接条件。
<many-to-one name="homeAddress" class="Address"
insert="false" update="false">
<column name="person_id" not-null="true" length="10"/>
<formula
>'MAILING'</formula>
</many-to-one
>
假设你的应用程序有两个同样名字的持久化类,但是你不想在 Hibernate 查询中使用他们的全限定名。除了依赖 auto-import="true" 以外,类也可以被显式地“import(引用)”。你甚至可以引用没有被明确映射的类和接口。
<import class="java.lang.Object" rename="Universe"/>
<import
class=
"ClassName"
rename
="ShortName"
/>
|
|
|
|
这是属性映射的又一种类型。<any> 映射元素定义了一种从多个表到类的多态关联。这种类型的映射常常需要多于一个字段。第一个字段持有被关联实体的类型,其他的字段持有标识符。对这种类型的关联来说,不可能指定一个外键约束,所以这当然不是映射(多态)关联的通常的方式。你只应该在非常特殊的情况下使用它(比如,审计 log,用户会话数据等等)。
meta-type 属性使得应用程序能指定一个将数据库字段的值映射到持久化类的自定义类型。这个持久化类包含有用 id-type 指定的标识符属性。你必须指定从 meta-type 的值到类名的映射。
<any name="being" id-type="long" meta-type="string">
<meta-value value="TBL_ANIMAL" class="Animal"/>
<meta-value value="TBL_HUMAN" class="Human"/>
<meta-value value="TBL_ALIEN" class="Alien"/>
<column name="table_name"/>
<column name="id"/>
</any
>
<any
name="
propertyName"
id-typ
e="idtypename"
meta-t
ype="metatypename"
cascad
e="cascade_style"
access
="field|property|ClassName"
optimi
stic-lock="true|false"
>
<meta-value ... />
<meta-value ... />
.....
<column .... />
<column .... />
.....
</any
>
|
|
|
|
|
|
|
|
|
|
|
|
和持久化服务相比,Java 级别的对象分为两个组别:
实体entity 独立于任何持有实体引用的对象。与通常的 Java 模型相比,不再被引用的对象会被当作垃圾收集掉。实体必须被显式的保存和删除(除非保存和删除是从父实体向子实体引发的级联)。这和 ODMG 模型中关于对象通过可触及保持持久性有一些不同 — 比较起来更加接近应用程序对象通常在一个大系统中的使用方法。实体支持循环引用和交叉引用,它们也可以加上版本信息。
一个实体的持久状态包含指向其他实体和值类型实例的引用。值可以是原始类型,集合(不是集合中的对象),组件或者特定的不可变对象。与实体不同,值(特别是集合和组件)是通过可触及性来进行持久化和删除的。因为值对象(和原始类型数据)是随着包含他们的实体而被持久化和删除的,他们不能被独立的加上版本信息。值没有独立的标识,所以他们不能被两个实体或者集合共享。
直到现在,我们都一直使用术语“持久类”(persistent class)来代表实体。我们仍然会这么做。然而严格说来,不是所有的用户自定义的,带有持久化状态的类都是实体。组件就是用户自定义类,却是值语义的。java.lang.String 类型的 java 属性也是值语义的。给了这个定义以后,我们可以说所有 JDK 提供的类型(类)都是值类型的语义,而用于自定义类型可能被映射为实体类型或值类型语义。采用哪种类型的语义取决于开发人员。在领域模型中,寻找实体类的一个好线索是共享引用指向这个类的单一实例,而组合或聚合通常被转化为值类型。
我们会在本文档中重复碰到这两个概念。
挑战在于将 java 类型系统(和开发者定义的实体和值类型)映射到 SQL/数据库类型系统。Hibernate 提供了连接两个系统之间的桥梁:对于实体类型,我们使用 <class>,<subclass> 等等。对于值类型,我们使用 <property>,<component> 及其他,通常跟随着 type 属性。这个属性的值是Hibernate 的映射类型的名字。Hibernate 提供了许多现成的映射(标准的 JDK 值类型)。你也可以编写自己的映射类型并实现自定义的变换策略,随后我们会看到这点。
所有的 Hibernate 内建类型,除了 collections 以外,都支持空(null)语义。
内置的 basic mapping types 可以大致地分类为:
integer, long, short, float, double, character, byte, boolean, yes_no, true_false这些类型都对应 Java 的原始类型或者其封装类,来符合(特定厂商的)SQL 字段类型。boolean, yes_no 和 true_false 都是 Java 中 boolean 或者 java.lang.Boolean 的另外说法。
string从 java.lang.String 到 VARCHAR(或者 Oracle 的 VARCHAR2)的映射。
date, time, timestamp从 java.util.Date 和其子类到 SQL 类型 DATE,TIME 和 TIMESTAMP(或等价类型)的映射。
calendar, calendar_date从 java.util.Calendar 到 SQL 类型 TIMESTAMP 和 DATE(或等价类型)的映射。
big_decimal, big_integer从 java.math.BigDecimal 和 java.math.BigInteger 到 NUMERIC(或者 Oracle 的 NUMBER类型)的映射。
locale, timezone, currency从 java.util.Locale,java.util.TimeZone 和 java.util.Currency 到 VARCHAR(或者 Oracle 的 VARCHAR2 类型)的映射。Locale 和 Currency 的实例被映射为它们的 ISO 代码。TimeZone 的实例被影射为它的 ID。
class从 java.lang.Class 到 VARCHAR(或者 Oracle 的 VARCHAR2 类型)的映射。Class 被映射为它的全限定名。
binary把字节数组(byte arrays)映射为对应的 SQL 二进制类型。
text把长 Java 字符串映射为 SQL 的 CLOB 或者 TEXT 类型。
serializable把可序列化的 Java 类型映射到对应的 SQL 二进制类型。你也可以为一个并非默认为基本类型的可序列化 Java 类或者接口指定 Hibernate 类型 serializable。
clob, blobJDBC 类 java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不适合使用这个类型,因为 blob 和 clob 对象可能在一个事务之外是无法重用的。(而且, 驱动程序对这种类型的支持充满着补丁和前后矛盾。)
imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date, imm_serializable, imm_binary
一般来说,映射类型被假定为是可变的 Java 类型,只有对不可变 Java 类型,Hibernate 会采取特定的优化措施,应用程序会把这些对象作为不可变对象处理。比如,你不应该对作为 imm_timestamp 映射的 Date 执行 Date.setTime()。要改变属性的值,并且保存这一改变,应用程序必须对这一属性重新设置一个新的(不一样的)对象。
实体及其集合的唯一标识可以是除了 binary、 blob 和 clob 之外的任何基础类型。(联合标识也是允许的,后面会说到。)
在 org.hibernate.Hibernate 中,定义了基础类型对应的 Type 常量。比如,Hibernate.STRING 代表 string 类型。
开发者创建属于他们自己的值类型也是很容易的。比如说,你可能希望持久化 java.lang.BigInteger 类型的属性,持久化成为 VARCHAR 字段。Hibernate没有内置这样一种类型。自定义类型能够映射一个属性(或集合元素)到不止一个数据库表字段。比如说,你可能有这样的 Java 属性:getName()/setName(),这是 java.lang.String 类型的,对应的持久化到三个字段:FIRST_NAME,INITIAL,SURNAME。
要实现一个自定义类型,可以实现 org.hibernate.UserType 或 org.hibernate.CompositeUserType 中的任一个,并且使用类型的 Java 全限定类名来定义属性。请查看 org.hibernate.test.DoubleStringType 这个例子,看看它是怎么做的。
<property name="twoStrings" type="org.hibernate.test.DoubleStringType">
<column name="first_string"/>
<column name="second_string"/>
</property
>
注意使用 <column> 标签来把一个属性映射到多个字段的做法。
CompositeUserType,EnhancedUserType,UserCollectionType 和 UserVersionType 接口为更特殊的使用方式提供支持。
你甚至可以在一个映射文件中提供参数给一个 UserType。 为了这样做,你的 UserType 必须实现 org.hibernate.usertype.ParameterizedType 接口。为了给自定义类型提供参数,你可以在映射文件中使用 <type> 元素。
<property name="priority">
<type name="com.mycompany.usertypes.DefaultValueIntegerType">
<param name="default"
>0</param>
</type>
</property
>
现在,UserType 可以从传入的 Properties 对象中得到 default 参数的值。
如果你非常频繁地使用某一 UserType,可以为他定义一个简称。这可以通过使用 <typedef> 元素来实现。Typedefs 为一自定义类型赋予一个名称,并且如果此类型是参数化的,还可以包含一系列默认的参数值。
<typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero">
<param name="default"
>0</param>
</typedef
>
<property name="priority" type="default_zero"/>
也可以根据具体案例通过属性映射中的类型参数覆盖在 typedef 中提供的参数。
尽管 Hibernate 内建的丰富的类型和对组件的支持意味着你可能很少 需要使用自定义类型。不过,为那些在你的应用中经常出现的(非实体)类使用自定义类型也是一个好方法。例如,一个 MonetaryAmount 类使用 CompositeUserType 来映射是不错的选择,虽然他可以很容易地被映射成组件。这样做的动机之一是抽象。使用自定义类型,以后假若你改变表示金额的方法时,它可以保证映射文件不需要修改。
对特定的持久化类,映射多次是允许的。这种情形下,你必须指定 entity name 来区别不同映射实体的对象实例。(默认情况下,实体名字和类名是相同的。) Hibernate 在操作持久化对象、编写查询条件,或者把关联映射到指定实体时,允许你指定这个 entity name(实体名字)。
<class name="Contract" table="Contracts"
entity-name="CurrentContract">
...
<set name="history" inverse="true"
order-by="effectiveEndDate desc">
<key column="currentContractId"/>
<one-to-many entity-name="HistoricalContract"/>
</set>
</class>
<class name="Contract" table="ContractHistory"
entity-name="HistoricalContract">
...
<many-to-one name="currentContract"
column="currentContractId"
entity-name="CurrentContract"/>
</class
>注意这里关联是如何用 entity-name 来代替 class 的。
你可通过在映射文档中使用反向引号(`)把表名或者字段名包围起来,以强制 Hibernate 在生成的 SQL 中把标识符用引号包围起来。Hibernate 会使用相应的 SQLDialect(方言)来使用正确的引号风格(通常是双引号,但是在 SQL Server 中是括号,MySQL 中是反向引号)。
<class name="LineItem" table="`Line Item`">
<id name="id" column="`Item Id`"/><generator class="assigned"/></id>
<property name="itemNumber" column="`Item #`"/>
...
</class
>
XML 并不适用于所有人, 因此有其他定义 Hibernate O/R 映射元数据(metadata)的方法。
很多 Hibernate 使用者更喜欢使用 XDoclet@hibernate.tags 将映射信息直接嵌入到源代码中。我们不会在本文档中涉及这个方法,因为严格说来,这属于 XDoclet 的一部分。然而,我们包含了如下使用 XDoclet 映射的 Cat 类的例子。
package eg;
import java.util.Set;
import java.util.Date;
/**
* @hibernate.class
* table="CATS"
*/
public class Cat {
private Long id; // identifier
private Date birthdate;
private Cat mother;
private Set kittens
private Color color;
private char sex;
private float weight;
/*
* @hibernate.id
* generator-class="native"
* column="CAT_ID"
*/
public Long getId() {
return id;
}
private void setId(Long id) {
this.id=id;
}
/**
* @hibernate.many-to-one
* column="PARENT_ID"
*/
public Cat getMother() {
return mother;
}
void setMother(Cat mother) {
this.mother = mother;
}
/**
* @hibernate.property
* column="BIRTH_DATE"
*/
public Date getBirthdate() {
return birthdate;
}
void setBirthdate(Date date) {
birthdate = date;
}
/**
* @hibernate.property
* column="WEIGHT"
*/
public float getWeight() {
return weight;
}
void setWeight(float weight) {
this.weight = weight;
}
/**
* @hibernate.property
* column="COLOR"
* not-null="true"
*/
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
/**
* @hibernate.set
* inverse="true"
* order-by="BIRTH_DATE"
* @hibernate.collection-key
* column="PARENT_ID"
* @hibernate.collection-one-to-many
*/
public Set getKittens() {
return kittens;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kittens.add(kitten);
}
/**
* @hibernate.property
* column="SEX"
* not-null="true"
* update="false"
*/
public char getSex() {
return sex;
}
void setSex(char sex) {
this.sex=sex;
}
}
参考 Hibernate 网站更多的 Xdoclet 和 Hibernate 的例子。
JDK 5.0 在语言级别引入了 XDoclet 风格的标注,并且是类型安全的,在编译期进行检查。这一机制比 XDoclet 的注解更为强大,有更好的工具和 IDE 支持。例如,IntelliJ IDEA,支持 JDK 5.0 注解的自动完成和语法高亮 。EJB 规范的新修订版 (JSR-220) 使用 JDK 5.0 的注解作为entity beans的主要元数据(metadata)机制。Hibernate 3 实现了JSR-220(the persistence API) 的 EntityManager,支持通过 Hibernate Annotations 包定义映射元数据。这个包作为单独的部分下载,支持 EJB3(JSR-220) 和 Hibernate3 的元数据。
这是一个被注解为 EJB entity bean 的 POJO 类的例子
@Entity(access = AccessType.FIELD)
public class Customer implements Serializable {
@Id;
Long id;
String firstName;
String lastName;
Date birthday;
@Transient
Integer age;
@Embedded
private Address homeAddress;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="CUSTOMER_ID")
Set<Order
> orders;
// Getter/setter and business methods
}
注意:对 JDK 5.0 注解(和 JSR-220)支持的工作仍然在进行中,并未完成。更多细节请参阅 Hibernate Annotations 模块。
Generated properties 指的是其值由数据库生成的属性。一般来说,如果对象有任何属性由数据库生成值,Hibernate 应用程序需要进行刷新(refresh)。但如果把属性标明为 generated,就可以转由 Hibernate 来负责这个动作。实际上。对定义了 generated properties 的实体,每当 Hibernate 执行一条 SQL INSERT 或者 UPDATE 语句,会立刻执行一条 select 来获得生成的值。
被标明为 generated 的属性还必须是 non-insertable 和 non-updateable 的。只有 versions、timestamps 和 simple properties 可以被标明为 generated。
never(默认)标明此属性值不是从数据库中生成。
insert — 标明此属性值在 insert 的时候生成,但是不会在随后的 update 时重新生成。比如说创建日期就归属于这类。注意虽然 version 和 timestamp 属性可以被标注为 generated,但是不适用这个选项。
always — 标明此属性值在 insert 和 update 时都会被生成。
Hibernate allows you to customize the SQL it uses to read and write the values of columns mapped to simple properties. For example, if your database provides a set of data encryption functions, you can invoke them for individual columns like this:
<!-- XML : generated by JHighlight v1.0 (http://jhighlight.dev.java.net) --> <span class="xml_tag_symbols"><</span><span class="xml_tag_name">property</span><span class="xml_plain"> </span><span class="xml_attribute_name">name</span><span class="xml_tag_symbols">=</span><span class="xml_attribute_value">"creditCardNumber"</span><span class="xml_tag_symbols">></span><span class="xml_plain"></span><br /> <span class="xml_plain"> </span><span class="xml_tag_symbols"><</span><span class="xml_tag_name">column</span><span class="xml_plain"> </span><br /> <span class="xml_plain"> </span><span class="xml_attribute_name">name</span><span class="xml_tag_symbols">=</span><span class="xml_attribute_value">"credit_card_num"</span><span class="xml_plain"></span><br /> <span class="xml_plain"> </span><span class="xml_attribute_name">read</span><span class="xml_tag_symbols">=</span><span class="xml_attribute_value">"decrypt(credit_card_num)"</span><span class="xml_plain"></span><br /> <span class="xml_plain"> </span><span class="xml_attribute_name">write</span><span class="xml_tag_symbols">=</span><span class="xml_attribute_value">"encrypt(?)"</span><span class="xml_tag_symbols">/></span><span class="xml_plain"></span><br /> <span class="xml_tag_symbols"></</span><span class="xml_tag_name">property</span><span class="xml_plain"></span><br /> <span class="xml_tag_symbols">></span><span class="xml_plain"></span><br />
每当属性在查询里被引用时,Hibernate 都自动应用自定义的表达式。这种功能和 derived-property formula 相似,但有两个不同的地方:
属性由一个或多个属性组成,它作为自动模式生成的一部分导出。
属性是可读写的,非只读的。
如果指定了 write 表达式,它必须只包含一个“?”占位符。
允许 CREATE 和 DROP 任意数据库对象,与 Hibernate 的 schema 交互工具组合起来,可以提供在 Hibernate 映射文件中完全定义用户 schema 的能力。虽然这是为创建和销毁 trigger(触发器)或stored procedure(存储过程)等特别设计的,实际上任何可以在 java.sql.Statement.execute() 方法中执行的 SQL 命令都可以在此使用(比如ALTER, INSERT,等等)。本质上有两种模式来定义辅助数据库对象...
第一种模式是在映射文件中显式声明 CREATE 和 DROP 命令:
<hibernate-mapping>
...
<database-object>
<create
>CREATE TRIGGER my_trigger ...</create>
<drop
>DROP TRIGGER my_trigger</drop>
</database-object>
</hibernate-mapping
>
第二种模式是提供一个类,这个类知道如何组织 CREATE 和 DROP 命令。这个特别类必须实现 org.hibernate.mapping.AuxiliaryDatabaseObject 接口。
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
</database-object>
</hibernate-mapping
>
还有,这些数据库对象可以特别指定为仅在特定的方言中才使用。
<hibernate-mapping>
...
<database-object>
<definition class="MyTriggerDefinition"/>
<dialect-scope name="org.hibernate.dialect.Oracle9iDialect"/>
<dialect-scope name="org.hibernate.dialect.Oracle10gDialect"/>
</database-object>
</hibernate-mapping
>
(译者注:在阅读本章的时候,以后整个手册的阅读过程中,我们都会面临一个名词方面的问题,那就是“集合”。"Collections" 和 "Set" 在中文里对应都被翻译为“集合”,但是他们的含义很不一样。Collections 是一个超集,Set 是其中的一种。大部分情况下,本译稿中泛指的未加英文注明的“集合”,都应当理解为“Collections”。在有些二者同时出现,可能造成混淆的地方,我们用“集合类”来特指“Collecions”,“集合(Set)”来指 "Set",一般都会在后面的括号中给出英文。希望大家在阅读时联系上下文理解,不要造成误解。 与此同时,“元素”一词对应的英文“element”,也有两个不同的含义。其一为集合的元素,是内存中的一个变量;另一含义则是 XML 文档中的一个标签所代表的元素。也请注意区别。本章中,特别是后半部分是需要反复阅读才能理解清楚的。如果遇到任何疑问,请记住,英文版本的 reference 是惟一标准的参考资料。) Hibernate 要求持久化集合值字段必须声明为接口,例如:
public class Product {
private String serialNumber;
private Set parts = new HashSet();
public Set getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
}
实际的接口可能是 java.util.Set、java.util.Collection、java.util.List、java.util.Map、java.util.SortedSet、java.util.SortedMap 或者任何你喜欢的类型("任何你喜欢的类型" 代表你需要编写 org.hibernate.usertype.UserCollectionType 的实现)。
注意我们是如何用一个 HashSet 实例来初始化实例变量的。这是用于初始化新创建(尚未持久化)的类实例中集合值属性的最佳方法。当你持久化这个实例时 — 比如通过调用 persist() — Hibernate 会自动把 HashSet 替换为 Hibernate 自己的 Set 实现。注意下面的错误:
Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); // Okay, kittens collection is a Set
(HashSet) cat.getKittens(); // Error!
根据不同的接口类型,被 Hibernate 注射的持久化集合类的表现类似 HashMap、HashSet、TreeMap、TreeSet 或 ArrayList。
集合类实例具有值类型的通常行为。当被持久化对象引用后,他们会自动被持久化,当不再被引用后,自动被删除。假若实例被从一个持久化对象传递到另一个,它的元素可能从一个表转移到另一个表。两个实体不能共享同一个集合类实例的引用。因为底层关系数据库模型的原因,集合值属性无法支持空值语义;Hibernate 对空的集合引用和空集合不加区别。
你不需要过多的为此担心。就如同你平时使用普通的 Java 集合类一样来使用持久化集合类。只是要确认你理解了双向关联的语义(后文将进行讨论)。
从集合类可以产生很大一部分映射,覆盖了很多常见的关系模型。我们建议你试验 schema 生成工具,来体会一下不同的映射声明是如何被翻译为数据库表的。
用于映射集合类的 Hibernate 映射元素取决于接口的类型。比如,<set> 元素用来映射 Set 类型的属性。
<class name="Product">
<id name="serialNumber" column="productSerialNumber"/>
<set name="parts">
<key column="productSerialNumber" not-null="true"/>
<one-to-many class="Part"/>
</set>
</class
>
除了 <set>,还有<list>,<map>,<bag>,<array> 和 <primitive-array> 映射元素。<map> 具有代表性:
<map
name="prop
ertyName"
table="tab
le_name"
schema="sc
hema_name"
lazy="true
|extra|false"
inverse="t
rue|false"
cascade="a
ll|none|save-update|delete|all-delete-orphan|delete-orphan"
sort="unso
rted|natural|comparatorClass"
order-by="
column_name asc|desc"
where="arb
itrary sql where condition"
fetch="joi
n|select|subselect"
batch-size
="N"
access="fi
eld|property|ClassName"
optimistic
-lock="true|false"
mutable="t
rue|false"
node="element-name|."
embed-xml="true|false"
>
<key .... />
<map-key .... />
<element .... />
</map
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
集合实例在数据库中依靠持有集合的实体的外键加以辨别。此外键作为集合关键字段(collection key column)(或多个字段)加以引用。集合关键字段通过 <key> 元素映射。
在外键字段上可能具有非空约束。对于大多数集合来说,这是隐含的。对单向一对多关联来说,外键字段默认是可以为空的,因此你可能需要指明 not-null="true"。
<key column="productSerialNumber" not-null="true"/>
外键约束可以使用 ON DELETE CASCADE。
<key column="productSerialNumber" on-delete="cascade"/>
对 <key> 元素的完整定义,请参阅前面的章节。
集合几乎可以包含任何其他的 Hibernate 类型,包括所有的基本类型、自定义类型、组件,当然还有对其他实体的引用。存在一个重要的区别:位于集合中的对象可能是根据“值”语义来操作(其声明周期完全依赖于集合持有者),或者它可能是指向另一个实体的引用,具有其自己的生命周期。在后者的情况下,被作为集合持有的状态考虑的,只有两个对象之间的“连接”。
被包容的类型被称为集合元素类型(collection element type)。集合元素通过 <element> 或 <composite-element> 映射,或在其是实体引用的时候,通过 <one-to-many> 或 <many-to-many> 映射。前两种用于使用值语义映射元素,后两种用于映射实体关联。
所有的集合映射,除了 set 和 bag 语义的以外,都需要指定一个集合表的索引字段(index column) — 用于对应到数组索引,或者 List 的索引,或者 Map 的关键字。通过 <map-key>,Map 的索引可以是任何基础类型;若通过 <map-key-many-to-many>,它也可以是一个实体引用;若通过 <composite-map-key>,它还可以是一个组合类型。数组或列表的索引必须是 integer 类型,并且使用 <list-index> 元素定义映射。被映射的字段包含有顺序排列的整数(默认从 0 开始)。
<list-index
column
="column_name"
base="
0|1|..."/>
| |
| |
<map-key
column
="column_name"
formul
a="any SQL expression"
type="
type_name"
node="@attribute-name"
length="N"/>
| |
| |
| |
<map-key-many-to-many
column
="column_name"
formul
a="any SQL expression"
class="ClassName"
/>
| |
| |
| |
假若你的表没有一个索引字段,当你仍然希望使用 List 作为属性类型,你应该把此属性映射为 Hibernate <bag>。从数据库中获取的时候,bag 不维护其顺序,但也可选择性的进行排序。
任何值集合或者多对多关联需要专用的具有一个或多个外键字段的 collection table、一个或多个 collection element column,以及还可能有一个或多个索引字段。
对于一个值集合,我们使用 <element> 标签。例如:
<element
column
="column_name"
formul
a="any SQL expression"
type="
typename"
length="L"
precision="P"
scale="S"
not-null="true|false"
unique="true|false"
node="element-name"
/>
| |
| |
| |
A many-to-many association is specified using the <many-to-many> element.
<many-to-many
column
="column_name"
formul
a="any SQL expression"
class=
"ClassName"
fetch=
"select|join"
unique
="true|false"
not-fo
und="ignore|exception"
entity
-name="EntityName"
proper
ty-ref="propertyNameFromAssociatedClass"
node="element-name"
embed-xml="true|false"
/>
| |
| |
| |
| |
| |
| |
| |
| |
下面是一些例子:
一系列字符串:
<set name="names" table="person_names">
<key column="person_id"/>
<element column="person_name" type="string"/>
</set
>
包含一组整数的 bag(还设置了 order-by 参数指定了迭代的顺序):
<bag name="sizes"
table="item_sizes"
order-by="size asc">
<key column="item_id"/>
<element column="size" type="integer"/>
</bag
>
一个实体数组,在这个案例中是一个多对多的关联(注意这里的实体是自动管理生命周期的对象(lifecycle objects),cascade="all"):
<array name="addresses"
table="PersonAddress"
cascade="persist">
<key column="personId"/>
<list-index column="sortOrder"/>
<many-to-many column="addressId" class="Address"/>
</array
>
一个 map,通过字符串的索引来指明日期:
<map name="holidays"
table="holidays"
schema="dbo"
order-by="hol_name asc">
<key column="id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map
>
一个组件的列表:(将在下一章讨论)
<list name="carComponents"
table="CarComponents">
<key column="carId"/>
<list-index column="sortOrder"/>
<composite-element class="CarComponent">
<property name="price"/>
<property name="type"/>
<property name="serialNumber" column="serialNum"/>
</composite-element>
</list
>
一对多关联通过外键连接两个类对应的表,而没有中间集合表。 这个关系模型失去了一些 Java 集合的语义:
一个被包含的实体的实例只能被包含在一个集合的实例中。
一个被包含的实体的实例只能对应于集合索引的一个值中。
一个从 Product 到 Part 的关联需要关键字字段,可能还有一个索引字段指向 Part 所对应的表。<one-to-many> 标记指明了一个一对多的关联。
<one-to-many
class=
"ClassName"
not-fo
und="ignore|exception"
entity
-name="EntityName"
node="element-name"
embed-xml="true|false"
/>
|
|
|
|
|
|
注意:<one-to-many> 元素不需要定义任何字段。也不需要指定表名。
重要提示:如果一对多关联中的外键字段定义成 NOT NULL,你必须把 <key> 映射声明为 not-null="true",或者使用双向关联,并且标明 inverse="true"。参阅本章后面关于双向关联的讨论。
下面的例子展示一个 Part 实体的 map,把 name 作为关键字。( partName 是 Part 的持久化属性)。注意其中的基于公式的索引的用法。
<map name="parts"
cascade="all">
<key column="productId" not-null="true"/>
<map-key formula="partName"/>
<one-to-many class="Part"/>
</map
>
Hibernate 支持实现 java.util.SortedMap 和 java.util.SortedSet 的集合。你必须在映射文件中指定一个比较器:
<set name="aliases"
table="person_aliases"
sort="natural">
<key column="person"/>
<element column="name" type="string"/>
</set>
<map name="holidays" sort="my.custom.HolidayComparator">
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date" type="date"/>
</map
>
sort 属性中允许的值包括 unsorted,natural 和某个实现了 java.util.Comparator 的类的名称。
分类集合的行为事实上象 java.util.TreeSet 或者 java.util.TreeMap。
如果你希望数据库自己对集合元素排序,可以利用 set,bag 或者 map 映射中的 order-by 属性。这个解决方案只能在 jdk1.4 或者更高的 jdk 版本中才可以实现(通过 LinkedHashSet 或者 LinkedHashMap 实现)。它是在 SQL 查询中完成排序,而不是在内存中。
<set name="aliases" table="person_aliases" order-by="lower(name) asc">
<key column="person"/>
<element column="name" type="string"/>
</set>
<map name="holidays" order-by="hol_date, hol_name">
<key column="year_id"/>
<map-key column="hol_name" type="string"/>
<element column="hol_date type="date"/>
</map
>
注意:这个 order-by 属性的值是一个 SQL 排序子句而不是 HQL 的。
关联还可以在运行时使用集合 filter() 根据任意的条件来排序:
sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();
双向关联允许通过关联的任一端访问另外一端。在 Hibernate 中,支持两种类型的双向关联:
Set 或者 bag 值在一端,单独值(非集合)在另外一端
两端都是 set 或 bag 值
要建立一个双向的多对多关联,只需要映射两个 many-to-many 关联到同一个数据库表中,并再定义其中的一端为 inverse(使用哪一端要根据你的选择,但它不能是一个索引集合)。
这里有一个 many-to-many 的双向关联的例子;每一个 category 都可以有很多 items,每一个 items 可以属于很多 categories:
<class name="Category">
<id name="id" column="CATEGORY_ID"/>
...
<bag name="items" table="CATEGORY_ITEM">
<key column="CATEGORY_ID"/>
<many-to-many class="Item" column="ITEM_ID"/>
</bag>
</class>
<class name="Item">
<id name="id" column="ITEM_ID"/>
...
<!-- inverse end -->
<bag name="categories" table="CATEGORY_ITEM" inverse="true">
<key column="ITEM_ID"/>
<many-to-many class="Category" column="CATEGORY_ID"/>
</bag>
</class
>
如果只对关联的反向端进行了改变,这个改变不会被持久化。 这表示 Hibernate 为每个双向关联在内存中存在两次表现,一个从 A 连接到 B,另一个从 B 连接到 A。如果你回想一下 Java 对象模型,我们是如何在 Java 中创建多对多关系的,这可以让你更容易理解:
category.getItems().add(item); // The category now "knows" about the relationship
item.getCategories().add(category); // The item now "knows" about the relationship
session.persist(item); // The relationship won't be saved!
session.persist(category); // The relationship will be saved
非反向端用于把内存中的表示保存到数据库中。
要建立一个一对多的双向关联,你可以通过把一个一对多关联,作为一个多对一关联映射到到同一张表的字段上,并且在"多"的那一端定义 inverse="true"。
<class name="Parent">
<id name="id" column="parent_id"/>
....
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class
>
在“一”这一端定义 inverse="true" 不会影响级联操作,二者是正交的概念。
对于有一端是 <list> 或者 <map> 的双向关联,需要加以特别考虑。假若子类中的一个属性映射到索引字段,没问题,我们仍然可以在集合类映射上使用 inverse="true":
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children" inverse="true">
<key column="parent_id"/>
<map-key column="name"
type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<property name="name"
not-null="true"/>
<many-to-one name="parent"
class="Parent"
column="parent_id"
not-null="true"/>
</class
>
但是,假若子类中没有这样的属性存在,我们不能认为这个关联是真正的双向关联(信息不对称,在关联的一端有一些另外一端没有的信息)。在这种情况下,我们不能使用 inverse="true"。我们需要这样用:
<class name="Parent">
<id name="id" column="parent_id"/>
....
<map name="children">
<key column="parent_id"
not-null="true"/>
<map-key column="name"
type="string"/>
<one-to-many class="Child"/>
</map>
</class>
<class name="Child">
<id name="id" column="child_id"/>
....
<many-to-one name="parent"
class="Parent"
column="parent_id"
insert="false"
update="false"
not-null="true"/>
</class
>
注意在这个映射中,关联中集合类"值"一端负责来更新外键。
有三种可能的途径来映射一个三重关联。第一种是使用一个 Map,把一个关联作为其索引:
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map
>
<map name="connections">
<key column="incoming_node_id"/>
<map-key-many-to-many column="outgoing_node_id" class="Node"/>
<many-to-many column="connection_id" class="Connection"/>
</map
>
第二种方法是简单的把关联重新建模为一个实体类。这使我们最经常使用的方法。
最后一种选择是使用复合元素,我们会在后面讨论。
如果你完全信奉我们对于“联合主键(composite keys)是个坏东西”,和“实体应该使用(无机的)自己生成的代用标识符(surrogate keys)”的观点,也许你会感到有一些奇怪,我们目前为止展示的多对多关联和值集合都是映射成为带有联合主键的表的!现在,这一点非常值得争辩;看上去一个单纯的关联表并不能从代用标识符中获得什么好处(虽然使用组合值的集合可能会获得一点好处)。不过,Hibernate 提供了一个(一点点试验性质的)功能,让你把多对多关联和值集合应得到一个使用代用标识符的表去。
<idbag> 属性让你使用 bag 语义来映射一个 List (或 Collection)。
<idbag name="lovers" table="LOVERS">
<collection-id column="ID" type="long">
<generator class="sequence"/>
</collection-id>
<key column="PERSON1"/>
<many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag
>
你可以理解,<idbag> 人工的 id 生成器,就好像是实体类一样!集合的每一行都有一个不同的人造关键字。但是,Hibernate 没有提供任何机制来让你取得某个特定行的人造关键字。
注意 <idbag> 的更新性能要比普通的 <bag> 高得多!Hibernate 可以有效的定位到不同的行,分别进行更新或删除工作,就如同处理一个 list,map 或者 set 一样。
在目前的实现中,还不支持使用 identity 标识符生成器策略来生成 <idbag> 集合的标识符。
集合例子(Collection example)。
下面的代码是用来添加一个新的 Child:
package eg;
import java.util.Set;
public class Parent {
private long id;
private Set children;
public long getId() { return id; }
private void setId(long id) { this.id=id; }
private Set getChildren() { return children; }
private void setChildren(Set children) { this.children=children; }
....
....
}
这个类有一个 Child 的实例集合。如果每一个子实例至多有一个父实例,那么最自然的映射是一个 one-to-many 的关联关系:
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping
>
在以下的表定义中反应了这个映射关系:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent
如果父亲是必须的,那么就可以使用双向 one-to-many 的关联了:
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
</class>
</hibernate-mapping
>
请注意 NOT NULL 的约束:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null
primary key,
name varchar(255),
parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent
另外,如果你绝对坚持这个关联应该是单向的,你可以对 <key> 映射声明 NOT NULL 约束:
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping
>
另外一方面,如果一个子实例可能有多个父实例,那么就应该使用 many-to-many 关联:
<hibernate-mapping>
<class name="Parent">
<id name="id">
<generator class="sequence"/>
</id>
<set name="children" table="childset">
<key column="parent_id"/>
<many-to-many class="Child" column="child_id"/>
</set>
</class>
<class name="Child">
<id name="id">
<generator class="sequence"/>
</id>
<property name="name"/>
</class>
</hibernate-mapping
>
表定义:
create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
child_id bigint not null,
primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references childFor more examples and a complete explanation of a parent/child relationship mapping, see 第 22 章 示例:父子关系(Parent/Child) for more information.
甚至可能出现更加复杂的关联映射,我们会在下一章中列出所有可能性。
关联关系映射通常情况是最难配置正确的。在这个部分中,我们从单向关系映射开始,然后考虑双向关系映射,逐步讲解典型的案例。在所有的例子中,我们都使将用 Person 和 Address。
我们根据映射关系是否涉及连接表以及多样性(multiplicity)来划分关联类型。
在传统的数据建模中,允许为 Null 值的外键被认为是一种不好的实践,因此我们所有的例子中都使用不允许为 Null 的外键。这并不是 Hibernate的 要求,即使你删除掉不允许为 Null 的约束,Hibernate 映射一样可以工作的很好。
单向 many-to-one 关联是最常见的单向关联关系。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
基于外键关联的单向一对一关联和单向多对一关联几乎是一样的。唯一的不同就是单向一对一关联中的外键字段具有唯一性约束。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
基于主键关联的单向一对一关联通常使用一个特定的 id 生成器,然而在这个例子中我们掉换了关联的方向:
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
</class>
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property"
>person</param>
</generator>
</id>
<one-to-one name="person" constrained="true"/>
</class
>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
基于外键关联的单向一对多关联是一种很少见的情况,我们不推荐使用它。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses">
<key column="personId"
not-null="true"/>
<one-to-many class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table Address ( addressId bigint not null primary key, personId bigint not null )
我们认为对于这种关联关系最好使用连接表。
基于连接表的单向一对多关联 应该优先被采用。请注意,通过指定unique="true",我们可以把多样性从多对多改变为一对多。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
基于连接表的单向多对一关联在关联关系可选的情况下应用也很普遍。例如:
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId" unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
基于连接表的单向一对一关联也是可行的,但非常少见。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
最后,这里是一个单向多对多关联的例子。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
双向多对一关联 是最常见的关联关系。下面的例子解释了这种标准的父/子关联关系。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true">
<key column="addressId"/>
<one-to-many class="Person"/>
</set>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
如果你使用 List(或者其他有序集合类),你需要设置外键对应的 key 列为 not null。Hibernate 将从集合端管理关联,维护每个元素的索引,并通过设置 update="false" 和 insert="false" 来对另一端反向操作。
<class name="Person">
<id name="id"/>
...
<many-to-one name="address"
column="addressId"
not-null="true"
insert="false"
update="false"/>
</class>
<class name="Address">
<id name="id"/>
...
<list name="people">
<key column="addressId" not-null="true"/>
<list-index column="peopleIdx"/>
<one-to-many class="Person"/>
</list>
</class
>
假若集合映射的 <key> 元素对应的底层外键字段是 NOT NULL 的,那么为这一 key 元素定义 not-null="true" 是很重要的。不要仅仅为可能的嵌套 <column>元素定义 not-null="true",<key> 元素也是需要的。
基于外键关联的双向一对一关联也很常见。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<one-to-one name="person"
property-ref="address"/>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
基于主键关联的一对一关联需要使用特定的 id 生成器:
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<one-to-one name="address"/>
</class>
<class name="Address">
<id name="id" column="personId">
<generator class="foreign">
<param name="property"
>person</param>
</generator>
</id>
<one-to-one name="person"
constrained="true"/>
</class
>
create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key )
下面是一个基于连接表的双向一对多关联的例子。注意 inverse="true" 可以出现在关联的任意一端,即 collection 端或者 join 端。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses"
table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
inverse="true"
optional="true">
<key column="addressId"/>
<many-to-one name="person"
column="personId"
not-null="true"/>
</join>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
基于连接表的双向一对一关联也是可行的,但极为罕见。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true">
<key column="personId"
unique="true"/>
<many-to-one name="address"
column="addressId"
not-null="true"
unique="true"/>
</join>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<join table="PersonAddress"
optional="true"
inverse="true">
<key column="addressId"
unique="true"/>
<many-to-one name="person"
column="personId"
not-null="true"
unique="true"/>
</join>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
create table Address ( addressId bigint not null primary key )
下面是一个双向多对多关联的例子。
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true" table="PersonAddress">
<key column="addressId"/>
<many-to-many column="personId"
class="Person"/>
</set>
</class
>
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
更复杂的关联连接极为罕见。通过在映射文档中嵌入 SQL 片断,Hibernate 也可以处理更为复杂的情况。比如,假若包含历史帐户数据的表定义了 accountNumber、effectiveEndDate 和 effectiveStartDate 字段,按照下面映射:
<properties name="currentAccountKey">
<property name="accountNumber" type="string" not-null="true"/>
<property name="currentAccount" type="boolean">
<formula
>case when effectiveEndDate is null then 1 else 0 end</formula>
</property>
</properties>
<property name="effectiveEndDate" type="date"/>
<property name="effectiveStateDate" type="date" not-null="true"/>
那么我们可以对目前(current)实例(其 effectiveEndDate 为 null)使用这样的关联映射:
<many-to-one name="currentAccountInfo"
property-ref="currentAccountKey"
class="AccountInfo">
<column name="accountNumber"/>
<formula
>'1'</formula>
</many-to-one
>
在更复杂的例子中,假想 Employee 和 Organization 之间的关联是通过一个 Employment 中间表维护的,而中间表中填充了很多历史雇员数据。那“雇员的最新雇主”这个关联(最新雇主就是具有最新的 startDate 的那个)可以这样映射:
<join>
<key column="employeeId"/>
<subselect>
select employeeId, orgId
from Employments
group by orgId
having startDate = max(startDate)
</subselect>
<many-to-one name="mostRecentEmployer"
class="Organization"
column="orgId"/>
</join
>
使用这一功能时可以充满创意和灵活性,但通常更加实用的是用 HQL 或条件查询来处理这些情况。
组件(Component)这个概念在 Hibernate 中几处不同的地方为了不同的目的被重复使用。
组件(Component)是一个被包含的对象,在持久化的过程中,它被当作值类型,而并非一个实体的引用。在这篇文档中,组件这一术语指的是面向对象的合成概念(而并不是系统构架层次上的组件的概念)。举个例子,你对人(Person)这个概念可以像下面这样来建模:
public class Person {
private java.util.Date birthday;
private Name name;
private String key;
public String getKey() {
return key;
}
private void setKey(String key) {
this.key=key;
}
public java.util.Date getBirthday() {
return birthday;
}
public void setBirthday(java.util.Date birthday) {
this.birthday = birthday;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
......
......
}
public class Name {
char initial;
String first;
String last;
public String getFirst() {
return first;
}
void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
void setLast(String last) {
this.last = last;
}
public char getInitial() {
return initial;
}
void setInitial(char initial) {
this.initial = initial;
}
}
在持久化的过程中,姓名(Name)可以作为人(Person)的一个组件。需要注意的是:你应该为姓名的持久化属性定义 getter 和 setter 方法,但是你不需要实现任何的接口或申明标识符字段。
以下是这个例子的 Hibernate 映射文件:
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name"
> <!-- class attribute optional -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class
>
人员(Person)表中将包括 pid,birthday,initial,first和 last 等字段。
就像所有的值类型一样,组件不支持共享引用。换句话说,两个人可能重名,但是两个 Person 对象应该包含两个独立的 Name 对象,只不过这两个 Name 对象具有“同样”的值。组件的值可以为空,其定义如下。 每当 Hibernate 重新加载一个包含组件的对象,如果该组件的所有字段为空,Hibernate 将假定整个组件为空。在大多数情况下,这样假定应该是没有问题的。
组件的属性可以是任意一种 Hibernate 类型(包括集合,多对多关联,以及其它组件等等)。嵌套组件不应该被当作一种特殊的应用(Nested components should not be considered an exotic usage)。Hibernate 倾向于支持细颗粒度的(fine-grained)对象模型。
<component> 元素允许加入一个 <parent> 子元素,在组件类内部就可以有一个指向其容器的实体的反向引用。
<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid"/>
</id>
<property name="birthday" type="date"/>
<component name="Name" class="eg.Name" unique="true">
<parent name="namedPerson"/> <!-- reference back to the Person -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
</class
>
Hibernate 支持组件的集合(例如:一个元素是姓名 Name 这种类型的数组)。你可以使用 <composite-element> 标签替代 <element> 标签来定义你的组件集合。
<set name="someNames" table="some_names" lazy="true">
<key column="id"/>
<composite-element class="eg.Name"
> <!-- class attribute required -->
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</composite-element>
</set
>
注意,如果你定义的 Set 包含组合元素(composite-element),正确地实现 equals() 和 hashCode() 是非常重要的。
组合元素可以包含组件,但是不能包含集合。如果你的组合元素自身包含组件,你必须使用 <nested-composite-element> 标签。这是一个相当特殊的案例 — 在一个组件的集合里,那些组件本身又可以包含其他的组件。这个时候你就应该考虑一下使用 one-to-many 关联是否会更恰当。尝试对这个组合元素重新建模为一个实体 — 但是需要注意的是,虽然 Java 模型和重新建模前是一样的,关系模型和持久性语义会有细微的变化。
请注意如果你使用 <set> 标签,一个组合元素的映射不支持可能为空的属性. 当删除对象时,Hibernate 必须使用每一个字段的值来确定一条记录(在组合元素表中,没有单独的关键字段),如果有为 null 的字段,这样做就不可能了。你必须作出一个选择,要么在组合元素中使用不能为空的属性,要么选择使用 <list>,<map>,<bag> 或者 <idbag> 而不是 <set>。
组合元素有个特别的用法是它可以包含一个<many-to-one>元素。类似这样的映射允许你将一个 many-to-many 关联表的额外字段映射为组合元素类。接下来的的例子是从 Order 到 Item 的一个多对多的关联关系,关联属性是 purchaseDate,price 和 quantity 。
<class name="eg.Order" .... >
....
<set name="purchasedItems" table="purchase_items" lazy="true">
<key column="order_id">