hibernate 官方入门教程


hibernate 官 方入 门教 程 第 一部 分 -第 一个 Hibernate 程序 首先我们将创建一个简单的控制台(console-based)Hibernate 程序。我们使用内置数据库 (in-memory database) (HSQLDB), 所以 我们 不必 安装 任何 数据 库服 务器 。 让我们假设我们希望有一个小程序可以保存我们希望关注的事件(Event)和这些事件的信 息。(译 者注 :在 本教 程的 后面 部分 ,我 们将 直接 使用 Event 而 不是 它的 中文 翻译 “事件”, 以 免混 淆 。) 我们做的第一件事是建立我们的开发目录,并把所有需要用到的Java 库文件放进去。从 Hibernate 网 站的 下载 页面 下载 Hibernate 分 发版 本 。解 压缩 包并 把 /lib 下 面的 所有 库文 件放 到 我们 新的 开发 目录 下面 的 /lib 目 录下 面。 看 起来 就像 这样 : .+lib antlr.jar cglib-full.jar asm.jar asm-attrs.jars commons-collections.jar commons-loggin g.jar ehcache.jar hibernate3.jar jta.jar dom4j.jar log4j.jar This is the minimum set of required libraries (note that we also copied hibernate3.jar, the main archive) for Hibernate. See the README.txt file in the lib/ directory of the Hibernate distribution for more information about required and optional third-party libraries. (Actually, Log4j is not required but preferred by many developers.) 这个是Hibernate 运行所需要的最小库文件集合 ( 注意 我们 也拷 贝了 Hibernate3.jar,这 个是 最重 要的 库 )。可 以在 Hibernate 分 发版 本的 lib/ 目 录下 查看 README.txt,以 获取 更多 关于 所需 和可 选的 第三 方库 文件 信息 (事 实上 ,Log4j 并 不是 必须 的库 文件 但是 许多 开发 者都 喜欢 用它 )。 接 下来 我们 创建 一个 类, 用来 代表 那些 我们 希望 储存 在数 据库 里面 的 event. 2.2.1. 第 一个 class 我 们的 第一 个持 久化 类是 一 个简 单的 JavaBean class,带 有一 些简 单的 属性 (property)。让 我 们来 看一 下代 码: import java.util.Date; public class Event { private Long id; private String title; private Date date; 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; } } 你 可以 看到 这个 class 对 属性 (property)的 存取 方法 (getter and setter method)使 用标 准 的JavaBean 命名约定,同时把内部字段(field)隐藏起来(private visibility)。这个是个受 推荐的设计方式,但并不是必须这样做。Hibernate 也可以直接访问这些字段(field),而 使 用访 问方 法( accessor method) 的好 处是 提供 了程 序重 构的 时候 健壮 性( robustness)。 id 属性(property)为 一个 Event 实 例提 供标 识属 性 (identifier property)的 值- 如 果我 们 希 望使 用 Hibernate 的 所有 特性 ,那 么我 们所 有的 持久 性实 体类 (persistent entity class)(这 里 也包 括一 些次 要依 赖类 ) 都 需要 一个 标识 属性 (identifier property)。而 事实 上 ,大 多数 应 用 程序 (特 别是 web 应 用程 序 )都 需要 识别 特定 的对 象 ,所 以你 应该 考 虑使 用标 识属 性而 不 是把 它当 作一 种限 制 。然而,我 们通 常不 会直 接操 作一 个对 象的 标识 符 (identifier),因 此标识符的setter 方法应该被声明为私有的(private)。这样当一个对象被保存的时候,只 有Hibernate可 以为 它分 配标 识符 。你 会发 现Hibernate可 以直 接访 问被 声明 为public,private 和protected 等不同级别访问控制的方法(accessor method)和字段(field)。所以选择哪 种 方式 来访 问属 性是 完全 取决 于你 ,你 可以 使你 的选 择与 你的 程序 设计 相吻 合。 所有的持久类(persistent classes)都要求有无参的构造器(no-argument constructor);因 为Hibernate 必 须要 使用 Java 反 射机 制( Reflection) 来实 例化 对象 。构 造器 ( constructor) 的 访问 控制 可以 是私 有的 (private),然 而当 生成 运行 时代 理 (runtime proxy)的 时候 将要 求 使用 至少 是 package 级 别的 访问 控制 ,这 样在 没有 字节 码编 入 (bytecode instrumentation) 的 情况 下, 从持 久化 类里 获取 数据 会更 有效 率一 些。 我们把这个Java 源代码文件放到我们的开发目录下面一个叫做src 的目录里。这个目录现 在 应该 看起 来像 这样 : .+lib +src Event.java 在 下一 步里 ,我 们将 把这 个持 久类 ( persisten class) 的信 息通 知 Hibernate 2.2.2. 映 射文 件 Hibernate 需要知道怎样去加载(load)和存储(store)我们的持久化类的对象。这里正是 Hibernate 映射文件(mapping file)发挥作用的地方。映射文件告诉Hibernate 它应该访问 数 据库 里面 的哪 个表 ( table) 和应 该使 用表 里面 的哪 些字 段( column)。 一 个映 射文 件的 基本 结构 看起 来像 这样 : [...] 注意Hibernate 的DTD 是非常复杂的。你可以在你的编辑器或者IDE 里面使用它来自动提 示 并完 成 (auto-completion)那 些用 来映 射的 XML 元素(element)和 属性 (attribute)。你 也 可以 用你 的文 本编 辑器 打开 DTD- 这是 最简 单的 方式 来浏 览所 有元 素和 参数 ,查 看它 们的 缺省值以及它们的注释,以得到一个整体的概观。同时也要注意Hibernate 不会从web 上 面获取DTD 文件,虽然XML 里面的URL 也许会建议它这样做,但是Hibernate 会首先查看 你 的程 序的 classpath。DTD 文 件被 包括 在 hibernate3.jar,同 时也 在 Hibernate 分 发版 的 src/ 路 径下 。 在以后的例子里面,我们将通过省略DTD 的声明来缩短代码长度。但是显然,在实际的程 序 中, DTD 声 明是 必须 的。 在 两个 hibernate-mapping 标签(tag)中间, 我 们包 含了 一个 class 元素(element)。所 有的 持 久性 实体 类( persistent entity classes)(再 次声 明, 这 里也 包括 那些 依赖 类, 就是 那些 次 要 的实 体 )都 需要 一个 这样 的映 射, 来映 射到 我们 的 SQL database。 我 们到 现在 为止 做的 一切 是告 诉 Hibernate 怎 样从 数据 库表 ( table)EVENTS 里 持久 化和 加 载Event 类 的对 象 ,每 个实 例对 应数 据库 里面 的一 行 。现 在我 们将 继续 讨论 有关 唯一 标识 属 性(unique identifier property) 的映 射。 另 外, 我们 不希 望去 考虑 怎样 产生 这个 标识 属性 , 我 们将 配置 Hibernate 的 标识 符生 成策 略( identifier generation strategy) 来产 生代 用主 键。 id元素是标识属性(identifer property)的声明,name="id" 声明了Java 属性(property) 的名字-Hibernate 将使用getId()和setId()来访问它。字段参数(column attribute)则告 诉Hibernate 我们使用EVENTS 表的哪个字段作为主键。嵌套的generator 元素指定了标识 符 的生 成策 略 -在 这里 我们 使用 increment, 这个 是非 常简 单的 在内 存中 直接 生成 数字 的 方法,多数用于测试(或教程)中。Hibernate 同时也支持使用数据库生成(database generated), 全局 唯一 性( globally unique) 和应 用程 序指 定( application assigned)( 或者 你 自己 为任 何已 有策 略所 写的 扩展 ) 这 些方 式来 生成 标识 符。 最 后我 们还 必须 在映 射文 件里 面包 括需 要持 久化 属性 的声 明 。缺 省的 情况 下 ,类 里面 的属 性 都 被视 为非 持久 化的 : 和id元 素类 似, property 元 素的 name 参数告诉Hibernate 使 用哪 个 getter 和setter 方法。 为 什么 date 属 性的 映射 包括 column 参数,但是title 却 没有 ? 当 没有 设定 column 参 数的 时 候,Hibernate 缺 省使 用属 性名 作为 字段 (column)名。对于title,这 样工 作得 很好 。然而, date 在 多数 的数 据库 里, 是一 个保 留关 键字 ,所 以我 们最 好把 它映 射成 另外 一个 名字 。 下 一件 有趣 的事 情 是title 属 性缺 少一 个type 参数。我 们声 明并 使用 在映 射文 件里 面 的type, 并不像我们假想的那样,是Java data type,同时也不是SQL database type。这些类型被称 作Hibernate mapping types,它们把数据类型从Java 转换到SQL data types。如果映射的参 数 没有 设置 的话 ,Hibernate 也 将尝 试去 确定 正确 的类 型转 换和 它的 映射 类型 。在 某些 情况 下 这个 自动 检测 (在 Java class 上 使用 反射 机制 )不 会产 生你 所期 待或 者 需 要的 缺省 值 。这 里 有个 例子 是关 于 date 属性。Hibernate 无 法知 道这 个属 性应 该被 映射 成下 面这 些类 型中 的 哪 一个 :SQL date,timestamp,time。我 们通 过声 明属 性映 射 timestamp 来 表示 我们 希望 保 存所 有的 关于 日期 和时 间的 信息 。 这个映射文件(mapping file)应该被保存为Event.hbm.xml,和我们的EventJava 源文件放 在 同一 个目 录下 。映 射文 件的 名字 可以 是任 意的 ,然而hbm.xml 已 经成 为 Hibernate 开 发者 社 区的 习惯 性约 定。 现 在目 录应 该看 起来 像这 样: .+lib +src Event.java Event.hbm.xml 我 们继 续进 行 Hibernate 的 主要 配置 。 2.2.3. Hibernate 配置 我 们现 在已 经有 了一 个持 久化 类和 它的 映射 文件 ,是 时候 配置 Hibernate 了 。在 我们 做这 个 之前,我 们需 要一 个数 据库 。HSQLDB,一个java-based 内 嵌式 SQL 数 据库 (in-memory SQL Database),可以从HSQLDB的 网站 上下 载 。实 际上 ,你 仅仅 需要 下载 /lib/目 录中 的hsqldb.jar。 把 这个 文件 放在 开发 文件 夹的 lib/目 录里 面。 在 开发 目录 下面 创建 一个 叫做 data 的 目录 -这 个是 HSQLDB存 储它 的数 据文 件的 地方 。 Hibernate 是你的程序里连接数据库的那个应用层,所以它需要连接用的信息。连接 (connection)是 通过 一个 也由 我们 配置 的 JDBC 连 接池 (connection pool)。Hibernate 的分 发 版里 面包 括了 一些 open source 的 连接 池, 但是 我们 已经 决定 在这 个教 程里 面使 用内 嵌式 连 接池 。 如 果你 希望 使用 一个 产品 级的 第三 方连 接池 软件 ,你 必须 拷贝 所需 的库 文件 去你 的classpath 并 使用 不同 的连 接池 设置 。 为 了配 置 Hibernate,我 们可 以使 用一 个简 单的 hibernate.properties 文件,或 者一 个稍 微复 杂的hibernate.cfg.xml,甚至可以完全使用程序来配置Hibernate。多数用户喜欢使用XML 配 置文 件: org.hsqldb.jdbcDriver jdbc:hsqldb:data/tutorial sa 1 org.hibernate.dialect.HSQLDialect true create 注 意这 个 XML 配 置使 用了 一个 不同 的 DTD。我 们配 置 Hibernate 的SessionFactory-一 个关 联于特定数据库全局性的工厂(factory)。如果你要使用多个数据库,通常应该在多个配置 文 件中 使用 多个 进 行配 置( 在更 早的 启动 步骤 中进 行 )。 最 开始 的 4个property 元 素包 含必 要的 JDBC 连 接信 息 。dialectproperty 表明Hibernate 应该 产 生针 对特 定数 据库 语法 的SQL 语句。hbm2ddl.auto 选 项将 自动 生成 数据 库表 定义 (schema) -直 接插 入数 据库 中。 当然 这个 选项 也可 以被 关闭 (通 过去 除这 个选 项) 或者 通过 Ant 任 务SchemaExport 来 把数 据库 表定 义导 入一 个文 件中 进行 优化 。最后,为 持久 化类 加入 映射 文 件。 把 这个 文件 拷贝 到源 代码 目录 下面 ,这 样它 就位 于 classpath 的root 路 径上 。Hibernate 在启 动 时会 自动 在 它的 根目 录开 始寻 找名 为 hibernate.cfg.xml 的 配置 文件 。 2.2.4. 用Ant 编译 在 这个 教程 里面 ,我 们将 用 Ant 来 编译 程序 。你 必须 先安 装 Ant- 可以 从 Ant download page 下 载它 。怎 样安 装 Ant 不 是这 个教 程的 内容 ,请 参考 Ant manual。当 你安 装完 了 Ant,我们 就 可以 开始 创建 编译 脚本 ,它 的文 件名 是 build.xml, 把它 直接 放在 开发 目录 下面 。 完善Ant 注意Ant 的分发版通常功能都是不完整的(就像Ant FAQ 里面说得那样),所以你常常不得 不需要自己动手来完善Ant。例如:如果你希望在你的build 文件里面使用JUnit 功能。为 了让JUnit 任务被激活(这个教程里面我们并不需要这个任务),你必须拷贝junit.jar 到 ANT_HOME/lib 目 录下 或者 删除 ANT_HOME/lib/ant-junit.jar 这 个插 件。 一 个基 本的 build 文 件看 起来 像这 样 这 个将 告诉 Ant 把 所有 在 lib 目 录下 以 .jar 结 尾的 文件 加入 classpath 中 用来 进行 编译 。它也 将 把所 有的 非 Java 源 代码 文件 ,例 如配 置和 Hibernate 映 射文 件 ,拷 贝到 目标 目录 下 。如果 你 现在 运行 Ant,你 将得 到以 下输 出: C:\hibernateTutorial\>antBuildfile: build.xmlcopy-resources: [copy] Copying 2 files to C:\hibernateTutorial\bincompile: [javac] Compiling 1 source file to C:\hibernateTutorial\binBUILD SUCCESSFULTotal time: 1 second 2.2.5. 安 装和 帮助 是时候来加载和储存一些Event 对象了,但是首先我们不得不完成一些基础的代码。我们 必须启动HibernateHibernateHibernateHibernate。这个启动过程包括创建一个全局性的SessoinFactory SessoinFactory SessoinFactory SessoinFactory 并把它储存在一 个应用程序容易访问的地方。SessionFactory SessionFactory SessionFactory SessionFactory 可以创建并打开新的SessionSessionSessionSession。一个SessioSessioSessioSessionnnn 代表一个单线程的单元操作,SessionFactory SessionFactory SessionFactory SessionFactory 则是一个线程安全的全局对象,只需要创建一 次。 我 们将 创建 一个 HibernateUtil 帮 助类 (helper class)来 负责 启动 Hibernate 并使操作Session 变 得容 易 。这 个帮 助类 将使 用被 称为 ThreadLocal Session 的 模式 来保 证当 前的 单元 操作 和当 前 线程 相关 联。 让我 们来 看一 眼它 的实 现: import org.hibernate.*; import org.hibernate.cfg.*; public class HibernateUtil { public static final SessionFactory sessionFactory; static { try { // Create the SessionFactory from hibernate.cfg.xml sessionFactory = 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 final ThreadLocal session = new ThreadLocal(); public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // Open a new Session, if this thread has none yet if (s == null) { s = sessionFactory.openSession(); // Store it in the ThreadLocal variable session.set(s); } return s; } public static void closeSession() throws HibernateException { Session s = (Session) session.get(); if (s != null) s.close(); session.set(null); } } 这个类不仅仅在它的静态初始化过程(仅当加载这个类的时候被JVM 执行一次)中产生全 局SessionFactory,同 时也 有一 个ThreadLocal变 量来 为当 前线 程保 存Session。不 论你 何时 调 用HibernateUtil.currentSession(),它 总是 返回 同一 个线 程中 的同 一 个Hibernate 单 元操 作 。而 一个HibernateUtil.closeSession()调 用将 终止 当前 线程 相联 系的 那个 单元 操作 。 在 你使 用这 个帮 助类 之前 ,确 定你 明白 Java 关 于本 地线 程变 量( thread-local variable) 的概 念。一个功能更加强大的HibernateUtil 帮助类可以在 CaveatEmptorhttp://caveatemptor.hibernate.org/找到-它同时也出现在书:《Hibernate in Action》 中。 注意 当你 把 Hibernate 部 署在 一个 J2EE 应 用服 务器 上的 时候 ,这 个类 不是 必须 的:一个Session 会 自动 绑定 到当 前的 JTA 事 物上 ,你 可以 通过 JNDI 来 查找 SessionFactory。 如 果你 使用 JBoss AS,Hibernate 可 以被 部署 成一 个受 管理 的系 统服 务 (system service)并自 动 绑定 SessionFactory 到JNDI 上。 把HibernateUtil.java 放 在开 发目 录的 源代 码路 径下 面, 与 Event.java 放 在一 起: .+lib +src Event.java Event.hbm.xml HibernateUtil.java hibernate.cfg.xml+databuild.xml 再次编译这个程序不应该有问题。最后我们需要配置一个日志系统-Hibernate 使用通用 日志接口,这允许你在Log4j 和JDK 1.4 logging 之间进行选择。多数开发者喜欢Log4j:从 Hibernate 的分发版(它在etc/ 目录下)拷贝log4j.properties 到你的src 目录,与 hibernate.cfg.xml.放 在一 起 。看 一眼 配置 示例 ,你 可以 修改 配置 如果 你希 望看 到更 多的 输出 信 息。 缺省 情况 下, 只有 Hibernate 的 启动 信息 会显 示在 标准 输出 上。 教 程的 基本 框架 完成 了 -现 在我 们可 以用 Hibernate 来 做些 真正 的工 作。 2.2.6. 加 载并 存储 对象 终于,我们可以使用Hibernate 来加载和存储对象了。我们编写一个带有main()方法的 EventManager 类: import org.hibernate.Transaction; import org.hibernate.Session; import java.util.Date; 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.sessionFactory.close(); } } 我 们从 命令 行读 入一 些参 数, 如果 第一 个参 数是 "store", 我们 创建 并储 存一 个新 的 Event: private void createAndStoreEvent(String title, Date theDate) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); session.save(theEvent); tx.commit(); HibernateUtil.closeSession(); } 我们创建一个新的Event 对象并把它传递给Hibernate。Hibernate 现在负责创建SQL 并把 INSERT 命 令传 给数 据库 。在 运行 它之 前 ,让 我们 花一 点时 间在 Session 和Transaction 的 处理 代 码上 。 每个Session 是 一个 独立 的单 元操 作。 你会 对我 们有 另外 一个 API:Transaction 而 感到 惊奇 。 这暗示一个单元操作可以拥有比一个单独的数据库事务更长的生命周期-想像在web 应 用 程序 中, 一个 单元 操作 跨越 多个 Http request/response 循环( 例如 一个 创建 对话 框 )。根 据“应用程序用户眼中的单元操作”来切割事务是Hibernate 的基本设计思想之一。我们调用 一 个长 生命 期的 单元 操作 Application Transaction 时,通 常包 装几 个更 生命 期较 短的 数据 库事 务。为了简化问题,在这个教程里我们使用Session 和Transaction 之间是1对1关系的粒 度(one-to-one granularity)。 Transaction.begin()和commit()都做些什么?rollback()在哪些情况下会产生错误?Hibernate 的Transaction API 实际上是可选的, 但是我们通常会为了便利性和可移植性而使用它。如 果你宁可自己处理数据库事务(例如,调用session.connection.commit()),通过直接和无管 理的JDBC,这样将把代码绑定到一个特定的部署环境中去。通过在Hibernate 配置中设置 Transaction 工厂,你 可以 把你 的持 久化 层部 署在 任何 地方 。查 看第 12 章事 务和 并发 了解 更 多关 于事 务处 理和 划分 的信 息。 在这 个例 子中 我们 也忽 略任 何异 常处 理和 事务 回滚 。 为了第一次运行我们的应用程序,我们必须增加一个可以调用的target 到Ant 的build 文件 中。 action 参 数的 值是 在通 过命 令行 调用 这个 target 的 时候 设置 的 : C:\hibernateTutorial\>ant run -Daction=store 你应该会看到,编译结束以后,Hibernate 根据你的配置启动,并产生一大堆的输出日志。 在 日志 最后 你会 看到 下面 这行 : [java] Hibernate: insert into EVENTS(EVENT_DATE, title, EVENT_ID) values (?,?,?) 这是Hibernate 执行的INSERT 命令,问号代表JDBC 的待绑定参数。如果想要看到绑定参数 的 值或 者减 少日 志的 长度 , 检 查你 在 log4j.properties 文 件里 的设 置。 现在我们想要列出所有已经被存储的event,所以我们增加一个条件分支选项到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.currentSession(); Transaction tx = session.beginTransaction(); List result = session.createQuery("from Event").list(); tx.commit(); session.close(); return result; } 我们在这里是用一个HQL(Hibernate Query Language-Hibernate 查询语言)查询语句来从 数据库中加载所有存在的Event。Hibernate 会生成正确的SQL,发送到数据库并使用查询 到 的数 据来 生成 Event 对 象。 当 然你 也可 以使 用 HQL 来 创建 更加 复杂 的查 询。 如果你现在使用命令行参数-Daction=list 来运行Ant,你会看到那些至今为止我们储存的 Event。如 果你 是一 直一 步步 的跟 随这 个教 程进 行的 ,你 也许 会吃 惊这 个并 不能 工作 -结 果 永远 为空 。原 因是 hbm2ddl.auto 打 开了 一个 Hibernate 的 配置 选项 :这 使得 Hibernate 会 在每次运行的时候重新创建数据库。通过从配置里删除这个选项来禁止它。运行了几次 store 之后,再运行list,你会看到结果出现在列表里。另外,自动生成数据库表并导出在 单 元测 试中 是非 常有 用的 。 2.3. 第 二部 分 -关 联映 射 我 们已 经映 射了 一个 持久 化实 体类 到一 个表 上 。让 我们 在这 个基 础上 增加 一些 类之 间的 关联 性。首先我们往我们程序里面增加人(people)的概念,并存储他们所参与的一个Event 列表。(译者注:与Event 一样,我们在后面的教程中将直接使用person 来表示“人”而不 是 它的 中文 翻译 ) 2.3.1. 映射Person 类 最 初的 Person 类 是简 单的 : public class Person { private Long id; private int age; private String firstname; private String lastname; Person() {} // Accessor methods for all properties, private setter for 'id' } Create a new mapping file called Person.hbm.xml: Finally, add the new mapping to Hibernate's configuration: 我 们现 在将 在这 两个 实体 类之 间创 建一 个关 联 。显然,person 可 以参 与一 系 列Event,而Event 也 有不 同的 参加 者 (person)。设 计上 面我 们需 要考 虑的 问题 是关 联的 方向 (directionality), 阶 数( multiplicity) 和集 合( collection) 的行 为。 2.3.2. 一 个单 向的 Set-based 关联 我 们将 向 Person 类 增加 一组 Event。这 样我 们可 以轻 松的 通过 调用 aPerson.getEvents() 得到 一个Person 所参与的Event 列表,而不必执行一个显式的查询。我们使用一个Java 的集合 类 :一 个 Set, 因为 Set 不 允许 包括 重复 的元 素而 且排 序和 我们 无关 。 目前为止我们设计了一个单向的,在一端有许多值与之对应的关联,通过Set 来实现。让 我 们为 这个 在 Java 类 里编 码并 映射 这个 关联 : public class Person { private Set events = new HashSet(); public Set getEvents() { return events; } public void setEvents(Set events) { this.events = events; } } 在 我们 映射 这个 关联 之前 ,先 考虑 这个 关联 另外 一端 。很 显然 的 ,我 们可 以保 持这 个关 联是 单 向的 。如 果我 们希 望这 个关 联是 双向 的 ,我 们可 以在 Event 里 创建 另外 一个 集合 ,例如: anEvent.getParticipants()。这 是留 给你 的一 个设 计选 项 ,但 是从 这个 讨论 中我 们可 以很 清楚 的了解什么是关联的阶数(multiplicity):在这个关联的两端都是“多”。我们叫这个为:多 对 多( many-to-many) 关联 。因 此, 我们 使用 Hibernate 的many-to-many 映 射: Hibernate 支 持所 有种 类的 集合 映射 ,是 最普 遍被 使用 的 。对 于多 对多 (many-to-many) 关联(或 者叫 n:m 实 体关 系 ), 需 要一 个用 来储 存关 联的 表 (association table)。表 里面 的每 一 行 代表 从一 个 person 到 一个 event 的 一个 关联 。 表 名是 由 set 元 素的 table 属 性值 配置 的 。 关联里面的标识字段名,person 的一端,是由元素定义,event 一端的字段名是由 元素的column 属性定义的。你也必须告诉Hibernate 集合中对象的类(也 就 是位 于这 个集 合所 代表 的关 联另 外一 端的 类 )。 这 个映 射的 数据 库表 定义 如下 : _______________________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | |_____________| |____________ ______| | PERSON | | | | | |_____________| | *EVENT_ID | <--> | *EVENT_ID | | | | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | | TITLE | |__________________| | AGE | |_____________ | | FIRSTNAME | | LASTNAME | |_____________| 2.3.3. 使 关联 工作 让 我们 把一 些 people 和event 放到EventManager 的 一个 新方 法中 : private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId); aPerson.getEvents().add(anEvent); tx.commit(); HibernateUtil.closeSession(); } 在 加载 一个 Person 和 一个 Event 之 后, 简单 的使 用普 通的 方法 修改 集合 。 如 你所 见, 没有 显式的update()或者save(), Hibernate 自动检测到集合已经被修改并需要保存。这个叫做 automatic dirty checking,你 也可 以尝 试修 改任 何对 象的 name 或者date 的 参数 。只 要他 们 处于persistent 状态,也 就是 被绑 定在 某个 Hibernate Session 上(例如:他们刚 刚在 一个 单 元 操作 从被 加载 或者 保存 ),Hibernate 监 视任 何改 变并 在后 台隐 式执 行 SQL。同 步内 存状 态 和 数据 库的 过程 ,通 常只 在 一 个单 元操 作结 束的 时候 发生 ,这 个过 程被 叫做 flushing。 你当然也可以在不同的单元操作里面加载person 和event。或者在一个Session 以外修改一 个不 是处 在持 久化 ( persistent) 状态 下的 对象 (如 果该 对象 以前 曾经 被持 久化 ,我 们称 这 个 状态 为脱 管( detached))。在 程序 里, 看起 来像 下面 这样 : private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId); tx.commit(); HibernateUtil.closeSession(); aPerson.getEvents().add(anEvent); // aPerson is detached Session session2 = HibernateUtil.currentSession(); Transaction tx2 = session.beginTransaction(); session2.update(aPerson); // Reattachment of aPerson tx2.commit(); HibernateUtil.closeSession(); } 对update 的调用使一个脱管对象(detached object)重新持久化,你可以说它被绑定到一 个 新的 单元 操作 上 ,所 以任 何你 对它 在脱 管 (detached)状 态下 所做 的修 改都 会被 保存 到数 据 库里 。 这 个对 我们 当前 的情 形不 是很 有用 ,但 是它 是非 常重 要的 概念 ,你 可以 把它 设计 进你 自己 的 程序中。现在,加进一个新的选项到EventManager 的main 方法中,并从命令行运行它来 完成这个练习。如果你需要一个person 和一个event 的标识符-save()返回它。******* 这 最后 一句 看不 明白 上 面是 一个 关于 两个 同等 地位 的类 间关 联的 例子 ,这 是在 两个 实体 之间 。像 前面 所提 到的 那 样,也存在其它的特别的类和类型,这些类和类型通常是“次要的”。其中一些你已经看到 过,好像int 或者String。我 们称 呼这 些类 为值 类型 (value type), 它 们的 实例 依赖 (depend) 在 某个 特定 的实 体上 。这 些类 型的 实例 没有 自己 的身 份 (identity),也 不能 在实 体间 共享 (比 如 两个 person 不 能引 用同 一个 firstname 对象,即 使他 们有 相同 的名 字 )。当然,value types 并 不仅 仅在 JDK 中 存在 (事 实上 ,在 一个 Hibernate 程 序中 ,所 有的 JDK 类 都被 视为 值类 型 ), 你 也可 以写 你自 己的 依赖 类, 例如 Address,MonetaryAmount。 你也可以设计一个值类型的集合(collection of value types),这个在概念上与实体的集合有 很 大的 不同 ,但 是在 Java 里 面看 起来 几乎 是一 样的 。 2.3.4. 值 类型 的集 合 我 们把 一个 值类 型对 象的 集合 加入 Person。我 们希 望保 存 email 地址,所 以我 们使 用 String, 而 这次 的集 合类 型又 是 Set: private Set emailAddresses = new HashSet();public Set getEmailAddresses() { return emailAddresses;}public void setEmailAddresses(Set emailAddresses) { this.emailAddresses = emailAddresses;} Set 的 映射 比较这次和较早先的映射,差别主要在element 部分这次并没有包括对其它实体类型的引 用,而是使用一个元素类型是String 的集合(这里使用小写的名字是向你表明它是一个 Hibernate 的映射类型或者类型转换器)。和以前一样,set 的table 参数决定用于集合的数 据库表名。key 元素定义了在集合表中使用的外键。element 元素的column 参数定义实际 保存String 值的 字段 名。 看 一下 修改 后的 数据 库表 定义 。 _______________________________ | | | | _____________ | 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 | |_____________| 你可以看到集合表(collection table)的主键实际上是个复合主键,同时使用了2个字段。 这也暗示了对于同一个person 不能有重复的email 地址,这正是Java 里面使用Set 时候所 需 要的 语义 ( Set 里 元素 不能 重复 )。 你现在可以试着把元素加入这个集合,就像我们在之前关联person 和event 的那样。Java 里 面的 代码 是相 同的 。 2.3.5. 双 向关 联 下 面我 们将 映射 一个 双向 关联 (bi-directional association)-在Java 里 面让 person 和event 可 以从 关联 的 任 何一 端访 问另 一端 。当 然, 数据 库表 定义 没有 改变 ,我 们仍 然需 要多 对多 (many-to-many) 的阶 数( multiplicity)。 一个 关系 型数 据库 要比 网络 编程 语言 更 加灵 活, 所以它并不需要任何像导航方向(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 里 面也 映射 这个 关联 。 如 你所 见 ,2个 映射 文件 里都 有通 常的 set 映射。注意key 和many-to-many 里 面的 字段 名在 两 个映 射文 件中 是交 换的 。这 里最 重要 的不 同是 Event 映 射文 件里 set 元 素的 inverse="true" 参 数。 这个表示Hibernate 需要在两个实体间查找关联信息的时候,应该使用关联的另外一端- Person 类。这 将会 极大 的帮 助你 理解 双向 关联 是如 何在 我们 的两 个实 体间 创建 的。 2.3.6. 使 双向 关联 工作 首先,请 牢记 在心 ,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)中 的类 以及 继承 自这 个类 的子 类 可 以访 问这 些方 法 ,但 是禁 止其 它的 直接 外部 访 问, 避免 了集 合的 内容 出现 混乱 。你 应该 尽可 能的 在集 合所 对应 的另 外一 端也 这样 做。 inverse 映射参数究竟表示什么呢?对于你和对于Java 来说,一个双向关联仅仅是在两端简 单 的设 置引 用 。然 而仅 仅这 样 Hibernate 并 没有 足够 的信 息去 正确 的产 生 INSERT 和UPDATE 语 句( 以避 免违 反数 据库 约束 ),所以Hibernate 需 要一 些帮 助来 正确 的处 理双 向关 联。 把 关联的一端设置为inverse 将告诉Hibernate 忽略关联的这一端,把这端看成是另外一端的 一个镜子(mirror)。这就是Hibernate 所需的信息,Hibernate 用它来处理如何把把一个数 据 导航 模型 映射 到关 系数 据库 表定 义。 你 仅仅 需要 记住 下面 这个 直观 的规 则: 所有 的双 向 关联需要有一端被设置为inverse。在一个一对多(one-to-many)关联中它必须是代表多 (many)的那端。而在多对多(many-to-many)关联中,你可以任意选取一端,两端之间 并 没有 差别 。 2.4. 总结 这 个教 程覆 盖了 关于 开发 一个 简单 的 Hibernate 应 用程 序的 几个 基础 方面 。 如 果你 已经 对 Hibernate 感 到自 信, 继续 浏览 开发 指南 里你 感兴 趣的 内容 -那 些会 被问 到的 问 题大 多是 事务 处理 (第12 章事 务和 并发 ),抓取(fetch)的 效率 (第20 章提 升性 能 ), 或者API 的 使用 (第11 章与 对象 共事 )和 查询 的特 性 (第11.4 节“查询”)。
还剩15页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 4 人已下载

下载pdf

pdf贡献者

zhaobin545

贡献于2012-02-13

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf