2007-06-29 19:55:14 +00:00

1234 lines
62 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<chapter id="tutorial">
<title>
Hibernate入门
</title>
<sect1 id="tutorial-intro" revision="1">
<title>
前言
</title>
<para>
本章是面向Hibernate初学者的一个入门教程。我们从一个使用驻留内存式(in-memory)数据库的简单命令行应用程序开始, 用易于理解的方式逐步开发。
</para>
<para>
本章面向Hibernate初学者但需要Java和SQL知识。它是在Michael Goegl所写的指南的基础上完成的。在这里我们称第三方库文件是指JDK 1.4和5.0。若使用JDK1.3,你可能需要其它的库文件。
</para>
<para>
本章的源代码已包含在发布包中,位于<literal>doc/reference/tutorial/</literal>目录下。
</para>
</sect1>
<sect1 id="tutorial-firstapp" revision="2">
<title>
第一部分 第一个Hibernate应用程序
</title>
<para>
首先我们将创建一个简单的基于控制台的(console-based)Hibernate应用程序。由于我们使用Java数据库(HSQL DB),所以不必安装任何数据库服务器。
</para>
<para>
假设我们希望有一个小应用程序可以保存我们希望参加的活动events和这些活动主办方的相关信息。
译者注在本教程的后面部分我们将直接使用event而不是它的中文翻译“活动”以免混淆。
</para>
<para>
我们所做的第一件事就是创建我们的开发目录并且把所有需要用到的Java库文件放进去。解压缩从Hibernate网站下载的Hibernate发布包并把<literal>/lib</literal>目录下所有需要的库文件拷到我们新建开发目录下的<literal>/lib</literal>目录下。看起来就像这样:
</para>
<programlisting><![CDATA[.
+lib
antlr.jar
cglib.jar
asm.jar
asm-attrs.jars
commons-collections.jar
commons-logging.jar
ehcache.jar
hibernate3.jar
jta.jar
dom4j.jar
log4j.jar ]]></programlisting>
<para>
<emphasis>到编写本文时为止</emphasis>这些是Hibernate运行所需要的最小库文件集合注意我们也拷贝了 Hibernate3.jar这个是最主要的文件。你正使用的Hibernate版本可能需要比这更多或少一些的库文件。请参见发布包中的<literal>lib/</literal>目录下的<literal>README.txt</literal>以获取更多关于所需和可选的第三方库文件信息事实上Log4j并不是必须的库文件但被许多开发者所喜欢
</para>
<para>
接下来我们创建一个类用来代表那些我们希望储存在数据库里的event。
</para>
<sect2 id="tutorial-firstapp-firstclass" revision="1">
<title>
第一个class
</title>
<para>
我们的第一个持久化类是一个带有一些属性property的简单JavaBean类
</para>
<programlisting><![CDATA[package events;
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;
}
}]]></programlisting>
<para>
你可以看到这个类对属性的存取方法getter and setter method使用了标准JavaBean命名约定同时把类属性field的访问级别设成私有的private。这是推荐的设计但并不是必须的。Hibernate也可以直接访问这些field而使用访问方法accessor method的好处是提供了重构时的健壮性robustness。为了通过反射机制Reflection来实例化这个类的对象我们需要提供一个无参的构造器no-argument constructor)。
</para>
<para>
对一特定的event, <literal>id</literal> 属性持有唯一的标识符identifier的值。如果我们希望使用Hibernate提供的所有特性那么所有的持久化实体persistent entity这里也包括一些次要依赖类都需要一个这样的标识符属性。而事实上大多数应用程序特别是web应用程序都需要通过标识符来区别对象所以你应该考虑使用标识符属性而不是把它当作一种限制。然而我们通常不会操作对象的标识identity因此它的setter方法的访问级别应该声明private。这样当对象被保存的时候只有Hibernate可以为它分配标识符值。你可看到Hibernate可以直接访问publicprivate和protected的访问方法和field。所以选择哪种方式完全取决于你你可以使你的选择与你的应用程序设计相吻合。
</para>
<para>
所有的持久化类persistent classes都要求有无参的构造器因为Hibernate必须使用Java反射机制来为你创建对象。构造器constructor的访问级别可以是private然而当生成运行时代理runtime proxy的时候则要求使用至少是package 级别的访问控制这样在没有字节码指令bytecode instrumentation的情况下从持久化类里获取数据会更有效率。
</para>
<para>
把这个Java源代码文件放到开发目录下的<literal>src</literal>目录里,注意包位置要正确。 现在这个目录看起来应该像这样:
</para>
<programlisting><![CDATA[.
+lib
<Hibernate and third-party libraries>
+src
+events
Event.java]]></programlisting>
<para>
下一步我们把这个持久化类的信息告诉Hibernate。
</para>
</sect2>
<sect2 id="tutorial-firstapp-mapping" revision="1">
<title>
映射文件
</title>
<para>
Hibernate需要知道怎样去加载load和存储store持久化类的对象。这正是Hibernate映射文件发挥作用的地方。映射文件告诉Hibernate它应该访问数据库(database)里面的哪个表table及应该使用表里面的哪些字段column
</para>
<para>
一个映射文件的基本结构看起来像这样:
</para>
<programlisting><![CDATA[<?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>
[...]
</hibernate-mapping>]]></programlisting>
<para>
注意Hibernate的DTD是非常复杂的。你的编辑器或者IDE里使用它来自动完成那些用来映射的XML元素element和属性attribute。你也可以在文本编辑器里打开DTD这是最简单的方式来概览所有的元素和attribute并查看它们的缺省值以及注释。注意Hibernate不会从web加载DTD文件但它会首先在应用程序的classpath中查找。DTD文件已包括在<literal>hibernate3.jar</literal>同时也在Hibernate发布包的<literal>src/</literal>目录下。
</para>
<para>
为缩短代码长度在以后的例子里我们会省略DTD的声明。当然在实际的应用程序中DTD声明是必须的。
</para>
<para>
<literal>hibernate-mapping</literal>标签tag之间, 含有一个<literal>class</literal>元素。所有的持久化实体类再次声明或许接下来会有依赖类就是那些次要的实体都需要一个这样的映射来把类对象映射到SQL数据库里的表。
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class name="events.Event" table="EVENTS">
</class>
</hibernate-mapping>]]></programlisting>
<para>
到目前为止我们告诉了Hibernate怎样把<literal>Events</literal>类的对象持久化到数据库的<literal>EVENTS</literal>表里,以及怎样从<literal>EVENTS</literal>表加载到<literal>Events</literal>类的对象。每个实例对应着数据库表中的一行。现在我们将继续讨论有关唯一标识符属性到数据库表的映射。另外由于我们不关心怎样处理这个标识符我们就配置由Hibernate的标识符生成策略来产生代理主键字段。
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class name="events.Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
</class>
</hibernate-mapping>]]></programlisting>
<para>
<literal>id</literal>元素是标识符属性的声明,<literal>name="id"</literal> 声明了Java属性的名字 Hibernate会使用<literal>getId()</literal><literal>setId()</literal>来访问它。 <literal>column</literal>属性则告诉Hibernate, 我们使用<literal>EVENTS</literal>表的哪个字段作为主键。嵌套的<literal>generator</literal>元素指定了标识符生成策略,在这里我们指定<literal>native</literal>它根据已配置的数据库方言自动选择最佳的标识符生成策略。Hibernate支持由数据库生成全局唯一性globally unique和应用程序指定或者你自己为任何已有策略所写的扩展这些策略来生成标识符。
</para>
<para>
最后我们在映射文件里面包含需要持久化属性的声明。默认情况下,类里面的属性都被视为非持久化的:
</para>
<programlisting><![CDATA[
<hibernate-mapping>
<class name="events.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>]]></programlisting>
<para>
<literal>id</literal>元素一样,<literal>property</literal>元素的<literal>name</literal>属性告诉Hibernate使用哪个getter和setter方法。在此例中Hibernate会寻找<literal>getDate()/setDate()</literal>, 以及<literal>getTitle()/setTitle()</literal>
</para>
<para>
为什么<literal>date</literal>属性的映射含有<literal>column</literal> attribute<literal>title</literal>却没有?当没有设定<literal>column</literal> attribute 的时候Hibernate缺省地使用JavaBean的属性名作为字段名。对于<literal>title</literal>,这样工作得很好。然而,<literal>date</literal>在多数的数据库里,是一个保留关键字,所以我们最好把它映射成一个不同的名字。
</para>
<para>
另一有趣的事情是<literal>title</literal>属性缺少一个<literal>type</literal> attribute。我们在映射文件里声明并使用的类型却不是我们期望的那样是Java数据类型同时也不是SQL数据库的数据类型。这些类型就是所谓的Hibernate 映射类型<emphasis>mapping types</emphasis>它们能把Java数据类型转换到SQL数据类型反之亦然。再次重申如果在映射文件中没有设置<literal>type</literal>属性的话Hibernate会自己试着去确定正确的转换类型和它的映射类型。在某些情况下这个自动检测机制在Java 类上使用反射机制)不会产生你所期待或需要的缺省值。<literal>date</literal>属性就是个很好的例子Hibernate无法知道这个属性<literal>java.util.Date</literal>类型的应该被映射成SQL <literal>date</literal>,或<literal>timestamp</literal>,还是<literal>time</literal> 字段。在此例中,把这个属性映射成<literal>timestamp</literal> 转换器,这样我们预留了日期和时间的全部信息。
</para>
<para>
应该把这个映射文件保存为<literal>Event.hbm.xml</literal>,且就在<literal>Event</literal>Java类的源文件目录下。映射文件可随意地命名<literal>hbm.xml</literal>的后缀已成为Hibernate开发者社区的约定。现在目录结构看起来应该像这样
</para>
<programlisting><![CDATA[.
+lib
<Hibernate and third-party libraries>
+src
+events
Event.java
Event.hbm.xml]]></programlisting>
<para>
我们继续进行Hibernate的主要配置。
</para>
</sect2>
<sect2 id="tutorial-firstapp-configuration" revision="2">
<title>
Hibernate配置
</title>
<para>
现在我们已经有了一个持久化类和它的映射文件该是配置Hibernate的时候了。在此之前我们需要一个数据库。 HSQL DB是种基于Java 的SQL数据库管理系统DBMS可以从HSQL DB的网站上下载。实际上你只需下载的包中的<literal>hsqldb.jar</literal>文件,并把这个文件放在开发文件夹的<literal>lib/</literal>目录下即可。
</para>
<para>
在开发的根目录下创建一个<literal>data</literal>目录 这是HSQL DB存储数据文件的地方。此时在data目录中运行<literal>java -classpath ../lib/hsqldb.jar org.hsqldb.Server</literal>就可启动数据库。你可以在log中看到它的启动及绑定到TCP/IP套结字这正是我们的应用程序稍后会连接的地方。如果你希望在本例中运行一个全新的数据库就在窗口中按下<literal>CTRL + C</literal>来关闭HSQL数据库并删除<literal>data/</literal>目录下的所有文件再重新启动HSQL数据库。
</para>
<para>
Hibernate是你的应用程序里连接数据库的那层所以它需要连接用的信息。连接connection是通过一个也由我们配置的JDBC连接池connection pool来完成的。Hibernate的发布包里包含了许多开源的open source连接池但在我们例子中使用Hibernate内置的连接池。注意如果你希望使用一个产品级(production-quality)的第三方连接池软件你必须拷贝所需的库文件到你的classpath下并使用不同的连接池设置。
</para>
<para>
为了保存Hibernate的配置我们可以使用一个简单的<literal>hibernate.properties</literal>文件,或者一个稍微复杂的<literal>hibernate.cfg.xml</literal>甚至可以完全使用程序来配置Hibernate。多数用户更喜欢使用XML配置文件
</para>
<programlisting><![CDATA[<?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">create</property>
<mapping resource="events/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration>]]></programlisting>
<para>
注意这个XML配置使用了一个不同的DTD。在这里我们配置了Hibernate的<literal>SessionFactory</literal>一个关联于特定数据库全局的工厂factory。如果你要使用多个数据库就要用多个的<literal>&lt;session-factory&gt;</literal>,通常把它们放在多个配置文件中(为了更容易启动)。
</para>
<para>
最开始的4个<literal>property</literal>元素包含必要的JDBC连接信息。方言dialect<literal>property</literal>元素指明Hibernate 生成的特定SQL变量。你很快会看到Hibernate对持久化上下文的自动session管理就会派上用场。 打开<literal>hbm2ddl.auto</literal>选项将自动生成数据库模式schema 直接加入数据库中。当然这个选项也可以被关闭通过去除这个配置选项或者通过Ant任务<literal>SchemaExport</literal>的帮助来把数据库schema重定向到文件中。最后在配置中为持久化类加入映射文件。
</para>
<para>
把这个文件拷贝到源代码目录下面这样它就位于classpath的根目录的最后。Hibernate在启动时会自动在classpath的根目录查找名为<literal>hibernate.cfg.xml</literal>的配置文件。
</para>
</sect2>
<sect2 id="tutorial-firstapp-ant" revision="1">
<title>
用Ant构建
</title>
<para>
现在我们用Ant来构建应用程序。你必须先安装Ant可以从<ulink url="http://ant.apache.org/bindownload.cgi">Ant 下载页面</ulink>得到它。怎样安装Ant就不在这里介绍了请参考<ulink url="http://ant.apache.org/manual/index.html">Ant 用户手册</ulink>。当你安装完了Ant就可以开始创建<literal>build.xml</literal>文件,把它直接放在开发目录下面。
</para>
<para>
一个简单的build文件看起来像这样
</para>
<programlisting><![CDATA[<project name="hibernate-tutorial" default="compile">
<property name="sourcedir" value="${basedir}/src"/>
<property name="targetdir" value="${basedir}/bin"/>
<property name="librarydir" value="${basedir}/lib"/>
<path id="libraries">
<fileset dir="${librarydir}">
<include name="*.jar"/>
</fileset>
</path>
<target name="clean">
<delete dir="${targetdir}"/>
<mkdir dir="${targetdir}"/>
</target>
<target name="compile" depends="clean, copy-resources">
<javac srcdir="${sourcedir}"
destdir="${targetdir}"
classpathref="libraries"/>
</target>
<target name="copy-resources">
<copy todir="${targetdir}">
<fileset dir="${sourcedir}">
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
</project>]]></programlisting>
<para>
这将告诉Ant把所有在lib目录下以<literal>.jar</literal>结尾的文件拷贝到classpath中以供编译之用。它也把所有的非Java源代码文件例如配置和Hibernate映射文件拷贝到目标目录。如果你现在运行Ant会得到以下输出
</para>
<programlisting><![CDATA[C:\hibernateTutorial\>ant
Buildfile: build.xml
copy-resources:
[copy] Copying 2 files to C:\hibernateTutorial\bin
compile:
[javac] Compiling 1 source file to C:\hibernateTutorial\bin
BUILD SUCCESSFUL
Total time: 1 second ]]></programlisting>
</sect2>
<sect2 id="tutorial-firstapp-helpers" revision="3">
<title>
启动和辅助类
</title>
<para>
是时候来加载和储存一些<literal>Event</literal>对象了但首先我们得编写一些基础的代码以完成设置。我们必须启动Hibernate此过程包括创建一个全局的<literal>SessoinFactory</literal>,并把它储存在应用程序代码容易访问的地方。<literal>SessionFactory</literal>可以创建并打开新的<literal>Session</literal>。一个<literal>Session</literal>代表一个单线程的单元操作,<literal>SessionFactory</literal>则是个线程安全的全局对象,只需要被实例化一次。
</para>
<para>
我们将创建一个<literal>HibernateUtil</literal>辅助类helper class来负责启动Hibernate和更方便地操作<literal>SessionFactory</literal>。让我们来看一下它的实现:
</para>
<programlisting><![CDATA[package util;
import org.hibernate.*;
import org.hibernate.cfg.*;
public class HibernateUtil {
private 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 SessionFactory getSessionFactory() {
return sessionFactory;
}
}]]></programlisting>
<para>
这个类不但在它的静态初始化过程仅当加载这个类的时候被JVM执行一次中产生全局的<literal>SessionFactory</literal>而且隐藏了它使用了静态singleton的事实。它也可能在应用程序服务器中的JNDI查找<literal>SessionFactory</literal>
</para>
<para>
如果你在配置文件中给<literal>SessionFactory</literal>一个名字,在<literal>SessionFactory</literal>创建后Hibernate会试着把它绑定到JNDI。要完全避免这样的代码你也可以使用JMX部署让具有JMX能力的容器来实例化<literal>HibernateService</literal>并把它绑定到JNDI。这些高级可选项在后面的章节中会讨论到。
</para>
<para>
<literal>HibernateUtil.java</literal>放在开发目录的源代码路径下,与放<literal>events</literal>的包并列:
</para>
<programlisting><![CDATA[.
+lib
<Hibernate and third-party libraries>
+src
+events
Event.java
Event.hbm.xml
+util
HibernateUtil.java
hibernate.cfg.xml
+data
build.xml]]></programlisting>
<para>
再次编译这个应用程序应该不会有问题。最后我们需要配置一个日志logging)系统 Hibernate使用通用日志接口允许你在Log4j和JDK 1.4 日志之间进行选择。多数开发者更喜欢Log4j从Hibernate的发布包中它在<literal>etc/</literal>目录下)拷贝<literal>log4j.properties</literal>到你的<literal>src</literal>目录,与<literal>hibernate.cfg.xml</literal>.放在一起。看一下配置示例如果你希望看到更加详细的输出信息你可以修改配置。默认情况下只有Hibernate的启动信息才会显示在标准输出上。
</para>
<para>
示例的基本框架完成了 现在我们可以用Hibernate来做些真正的工作。
</para>
</sect2>
<sect2 id="tutorial-firstapp-workingpersistence" revision="5">
<title>
加载并存储对象
</title>
<para>
我们终于可以使用Hibernate来加载和存储对象了编写一个带有<literal>main()</literal>方法的<literal>EventManager</literal>类:
</para>
<programlisting><![CDATA[package events;
import org.hibernate.Session;
import java.util.Date;
import 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();
}
}]]></programlisting>
<para>
我们创建了个新的<literal>Event</literal>对象并把它传递给Hibernate。现在Hibernate负责与SQL打交道并把<literal>INSERT</literal>命令传给数据库。在运行之前,让我们看一下处理<literal>Session</literal><literal>Transaction</literal>的代码。
</para>
<para>
一个<literal>Session</literal>就是个单一的工作单元。我们暂时让事情简单一些并假设Hibernate<literal>Session</literal>和数据库事务是一一对应的。为了让我们的代码从底层的事务系统中脱离出来此例中是JDBC但也可能是JTA我们使用Hibernate <literal>Session</literal>中的<literal>Transaction</literal> API。
</para>
<para>
<literal>sessionFactory.getCurrentSession()</literal>是干什么的呢?首先,只要你持有<literal>SessionFactory</literal>(幸亏我们有<literal>HibernateUtil</literal>,可以随时获得),大可在任何时候、任何地点调用这个方法。<literal>getCurrentSession()</literal>方法总会返回“当前的”工作单元。记得我们在<literal>hibernate.cfg.xml</literal>中把这一配置选项调整为"thread"了吗因此因此当前工作单元被绑定到当前执行我们应用程序的Java线程。但是这并非是完全准确的,你还得考虑工作单元的生命周期范围 (scope),它何时开始,又何时结束.
</para>
<para>
<literal>Session</literal>在第一次被使用的时候,即第一次调用<literal>getCurrentSession()</literal>的时候,其生命周期就开始。然后它被Hibernate绑定到当前线程。当事务结束的时候不管是提交还是回滚Hibernate会自动把<literal>Session</literal>从当前线程剥离,并且关闭它。假若你再次调用<literal>getCurrentSession()</literal>,你会得到一个新的<literal>Session</literal>,并且开始一个新的工作单元。这种<emphasis>线程绑定(thread-bound)</emphasis>的编程模型model是使用Hibernate的最广泛的方式,因为它支持对你的代码灵活分层(事务划分可以和你的数据访问代码分离开来,在本教程的后面部分就会这么做)。
</para>
<para>
和工作单元的生命周期这个话题相关Hibernate <literal>Session</literal>是否被应该用来执行多次数据库操作?上面的例子对每一次操作使用了一个<literal>Session</literal>这完全是巧合这个例子不是很复杂无法展示其他方式。Hibernate <literal>Session</literal>的生命周期可以很灵活,但是你绝不要把你的应用程序设计成为<emphasis>每一次</emphasis>数据库操作都用一个新的Hibernate <literal>Session</literal>。因此就算下面的例子(它们都很简单)中你可以看到这种用法,记住<emphasis>每次操作一个session</emphasis>是一个反模式。在本教程的后面会展示一个真正的(web)程序。
</para>
<para>
关于事务处理及事务边界界定的详细信息,请参看<xref linkend="transactions"/>。在上面的例子中,我们也忽略了所有的错误与回滚的处理。
</para>
<para>
为第一次运行我们的程序我们得在Ant的build文件中增加一个可以调用得到的target。
</para>
<programlisting><![CDATA[<target name="run" depends="compile">
<java fork="true" classname="events.EventManager" classpathref="libraries">
<classpath path="${targetdir}"/>
<arg value="${action}"/>
</java>
</target>]]></programlisting>
<para>
<literal>action</literal>参数argument的值是通过命令行调用这个target的时候设置的
</para>
<programlisting><![CDATA[C:\hibernateTutorial\>ant run -Daction=store]]></programlisting>
<para>
你应该会看到编译以后Hibernate根据你的配置启动并产生一大堆的输出日志。在日志最后你会看到下面这行
</para>
<programlisting><![CDATA[[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)]]></programlisting>
<para>
这是Hibernate执行的<literal>INSERT</literal>命令问号代表JDBC的绑定参数。如果想要看到绑定参数的值或者减少日志的长度就要调整你在<literal>log4j.properties</literal>文件里的设置。
</para>
<para>
我们想要列出所有已经被存储的events就要增加一个条件分支选项到main方法中去。
</para>
<programlisting><![CDATA[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());
}
}]]></programlisting>
<para>
我们也增加一个新的<literal>listEvents()</literal>方法:
</para>
<programlisting><![CDATA[private List listEvents() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result = session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}]]></programlisting>
<para>
我们在这里是用一个HQLHibernate Query LanguageHibernate查询语言查询语句来从数据库中加载所有存在的<literal>Event</literal>对象。Hibernate会生成适当的SQL把它发送到数据库并操作从查询得到数据的<literal>Event</literal>对象。当然你可以使用HQL来创建更加复杂的查询。
</para>
<para>
现在,根据以下步骤来执行并测试以上各项:
</para>
<itemizedlist>
<listitem>
<para>
运行<literal>ant run -Daction=store</literal>来保存一些内容到数据库。当然先得用hbm2ddl来生成数据库schema。
</para>
</listitem>
<listitem>
<para>
现在把<literal>hibernate.cfg.xml</literal>文件中hbm2ddl属性注释掉这样我们就取消了在启动时用hbm2ddl来生成数据库schema。通常只有在不断重复进行单元测试的时候才需要打开它但再次运行hbm2ddl会把你保存的一切都删掉<emphasis>drop</emphasis>)——<literal>create</literal>配置的真实含义是“在创建SessionFactory的时候从schema 中drop 掉所有的表,再重新创建它们”。
</para>
</listitem>
</itemizedlist>
<para>
如果你现在使用命令行参数<literal>-Daction=list</literal>运行Ant你会看到那些至今为止我们所储存的events。当然你也可以多调用几次<literal>store</literal>以保存更多的envents。
</para>
<para>
注意很多Hibernate新手在这一步会失败我们不时看到关于<emphasis>Table not found</emphasis>错误信息的提问。但是只要你根据上面描述的步骤来执行就不会有这个问题因为hbm2ddl会在第一次运行的时候创建数据库schema后继的应用程序重起后还能继续使用这个schema。假若你修改了映射或者修改了数据库schema你必须把hbm2ddl重新打开一次。
</para>
</sect2>
</sect1>
<sect1 id="tutorial-associations">
<title>
第二部分 关联映射
</title>
<para>
我们已经映射了一个持久化实体类到表上。让我们在这个基础上增加一些类之间的关联。首先我们往应用程序里增加人people的概念并存储他们所参与的一个Event列表。译者注与Event一样我们在后面将直接使用person来表示“人”而不是它的中文翻译
</para>
<sect2 id="tutorial-associations-mappinguser" revision="1">
<title>
映射Person类
</title>
<para>
最初简单的<literal>Person</literal>类:
</para>
<programlisting><![CDATA[package events;
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'
}]]></programlisting>
<para>
创建一个名为<literal>Person.hbm.xml</literal>的新映射文件别忘了最上面的DTD引用
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class name="events.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>]]></programlisting>
<para>
最后把新的映射加入到Hibernate的配置中
</para>
<programlisting><![CDATA[<mapping resource="events/Event.hbm.xml"/>
<mapping resource="events/Person.hbm.xml"/>]]></programlisting>
<para>
现在我们在这两个实体之间创建一个关联。显然persons可以参与一系列events而events也有不同的参加者persons。我们需要处理的设计问题是关联方向directionality阶数multiplicity和集合collection的行为。
</para>
</sect2>
<sect2 id="tutorial-associations-unidirset" revision="3">
<title>
单向Set-based的关联
</title>
<para>
我们将向<literal>Person</literal>类增加一连串的events。那样通过调用<literal>aPerson.getEvents()</literal>就可以轻松地导航到特定person所参与的events而不用去执行一个显式的查询。我们使用Java的集合类collection<literal>Set</literal>因为set 不包含重复的元素及与我们无关的排序。
</para>
<para>
我们需要用set 实现一个单向多值关联。让我们在Java类里为这个关联编码接着映射它
</para>
<programlisting><![CDATA[public class Person {
private Set events = new HashSet();
public Set getEvents() {
return events;
}
public void setEvents(Set events) {
this.events = events;
}
}]]></programlisting>
<para>
在映射这个关联之前,先考虑一下此关联的另外一端。很显然,我们可以保持这个关联是单向的。或者,我们可以在<literal>Event</literal>里创建另外一个集合,如果希望能够双向地导航,如:<literal>anEvent.getParticipants()</literal>。从功能的角度来说这并不是必须的。因为你总可以显式地执行一个查询以获得某个特定event的所有参与者。这是个在设计时需要做出的选择完全由你来决定但此讨论中关于关联的阶数是清楚的即两端都是“多”值的我们把它叫做<emphasis>多对多(many-to-many)</emphasis>关联。因而我们使用Hibernate的多对多映射
</para>
<programlisting><![CDATA[<class name="events.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="events.Event"/>
</set>
</class>]]></programlisting>
<para>
Hibernate支持各种各样的集合映射<literal>&lt;set&gt;</literal>使用的最为普遍。对于多对多关联(或叫<emphasis>n:m</emphasis>实体关系), 需要一个关联表association table<literal></literal>里面的每一行代表从person到event的一个关联。表名是由<literal>set</literal>元素的<literal>table</literal>属性配置的。关联里面的标识符字段名对于person的一端是由<literal>&lt;key&gt;</literal>元素定义而event一端的字段名是由<literal>&lt;many-to-many&gt;</literal>元素的<literal>column</literal>属性定义。你也必须告诉Hibernate集合中对象的类也就是位于这个集合所代表的关联另外一端的类
</para>
<para>
因而这个映射的数据库schema是
</para>
<programlisting><![CDATA[
_____________ __________________
| | | | _____________
| EVENTS | | PERSON_EVENT | | |
|_____________| |__________________| | PERSON |
| | | | |_____________|
| *EVENT_ID | <--> | *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
]]></programlisting>
</sect2>
<sect2 id="tutorial-associations-working" revision="2">
<title>
使关联工作
</title>
<para>
我们把一些people和events 一起放到<literal>EventManager</literal>的新方法中:
</para>
<programlisting><![CDATA[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();
}]]></programlisting>
<para>
在加载一<literal>Person</literal><literal>Event</literal>后,使用普通的集合方法就可容易地修改我们定义的集合。如你所见,没有显式的<literal>update()</literal><literal>save()</literal>Hibernate会自动检测到集合已经被修改并需要更新回数据库。这叫做自动脏检查<emphasis>automatic dirty checking</emphasis>你也可以尝试修改任何对象的name或者date属性只要他们处于<emphasis>持久化</emphasis>状态也就是被绑定到某个Hibernate 的<literal>Session</literal>他们刚刚在一个单元操作被加载或者保存Hibernate监视任何改变并在后台隐式写的方式执行SQL。同步内存状态和数据库的过程通常只在单元操作结束的时候发生称此过程为清理缓存<emphasis>flushing</emphasis>。在我们的代码中,工作单元由数据库事务的提交(或者回滚)来结束——这是由<literal>CurrentSessionContext</literal>类的<literal>thread</literal>配置选项定义的。
</para>
<para>
当然你也可以在不同的单元操作里面加载person和event。或在<literal>Session</literal>以外修改不是处在持久化persistent状态下的对象如果该对象以前曾经被持久化那么我们称这个状态为<emphasis>脱管detached</emphasis>)。你甚至可以在一个集合被脱管时修改它:
</para>
<programlisting><![CDATA[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();
}]]></programlisting>
<para>
<literal>update</literal>的调用使一个脱管对象重新持久化,你可以说它被绑定到一个新的单元操作上,所以在脱管状态下对它所做的任何修改都会被保存到数据库里。这也包括你对这个实体对象的集合所作的任何改动(增加/删除)。
</para>
<para>
这对我们当前的情形不是很有用,但它是非常重要的概念,你可以把它融入到你自己的应用程序设计中。在<literal>EventManager</literal>的main方法中添加一个新的动作并从命令行运行它来完成我们所做的练习。如果你需要person及event的标识符 — 那就用<literal>save()</literal>方法返回它(你可能需要修改前面的一些方法来返回那个标识符):
</para>
<programlisting><![CDATA[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);
}]]></programlisting>
<para>
上面是个关于两个同等重要的实体类间关联的例子。像前面所提到的那样,在特定的模型中也存在其它的类和类型,这些类和类型通常是“次要的”。你已看到过其中的一些,像<literal>int</literal><literal>String</literal>。我们称这些类为<emphasis>值类型value type</emphasis>,它们的实例<emphasis>依赖depend</emphasis>在某个特定的实体上。这些类型的实例没有它们自己的标识identity也不能在实体间被共享比如两个person不能引用同一个<literal>firstname</literal>对象即使他们有相同的first name。当然值类型并不仅仅在JDK中存在事实上在一个Hibernate应用程序中所有的JDK类都被视为值类型而且你也可以编写你自己的依赖类例如<literal>Address</literal><literal>MonetaryAmount</literal>
</para>
<para>
你也可以设计一个值类型的集合这在概念上与引用其它实体的集合有很大的不同但是在Java里面看起来几乎是一样的。
</para>
</sect2>
<sect2 id="tutorial-associations-valuecollections">
<title>
值类型的集合
</title>
<para>
我们把一个值类型对象的集合加入<literal>Person</literal>实体中。我们希望保存email地址所以使用<literal>String</literal>类型,而且这次的集合类型又是<literal>Set</literal>
</para>
<programlisting><![CDATA[private Set emailAddresses = new HashSet();
public Set getEmailAddresses() {
return emailAddresses;
}
public void setEmailAddresses(Set emailAddresses) {
this.emailAddresses = emailAddresses;
}]]></programlisting>
<para>
这个<literal>Set</literal>的映射
</para>
<programlisting><![CDATA[<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
<key column="PERSON_ID"/>
<element type="string" column="EMAIL_ADDR"/>
</set>]]></programlisting>
<para>
比较这次和此前映射的差别,主要在于<literal>element</literal>部分,这次并没有包含对其它实体引用的集合,而是元素类型为<literal>String</literal>的集合在映射中使用小写的名字”string“是向你表明它是一个Hibernate的映射类型或者类型转换器。和之前一样<literal>set</literal>元素的<literal>table</literal>属性决定了用于集合的表名。<literal>key</literal>元素定义了在集合表中外键的字段名。<literal>element</literal>元素的<literal>column</literal>属性定义用于实际保存<literal>String</literal>值的字段名。
</para>
<para>
看一下修改后的数据库schema。
</para>
<programlisting><![CDATA[
_____________ __________________
| | | | _____________
| 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 |
|_____________|
]]></programlisting>
<para>
你可以看到集合表的主键实际上是个复合主键同时使用了2个字段。这也暗示了对于同一个person不能有重复的email地址这正是Java里面使用Set时候所需要的语义Set里元素不能重复
</para>
<para>
你现在可以试着把元素加入到这个集合就像我们在之前关联person和event的那样。其实现的Java代码是相同的
</para>
<programlisting><![CDATA[private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
// The getEmailAddresses() might trigger a lazy load of the collection
aPerson.getEmailAddresses().add(emailAddress);
session.getTransaction().commit();
}]]></programlisting>
<para>
这次我们没有使用<emphasis>fetch</emphasis>查询来初始化集合。因此调用其getter方法会触发另一附加的select来初始化集合这样我们才能把元素添加进去。检查SQL log试着通过预先抓取来优化它。
</para>
</sect2>
<sect2 id="tutorial-associations-bidirectional" revision="1">
<title>
双向关联
</title>
<para>
接下来我们将映射双向关联bi-directional association 在Java里让person和event可以从关联的任何一端访问另一端。当然数据库schema没有改变我们仍然需要多对多的阶数。一个关系型数据库要比网络编程语言 更加灵活所以它并不需要任何像导航方向navigation direction的东西 数据可以用任何可能的方式进行查看和获取。
</para>
<para>
首先把一个参与者person的集合加入<literal>Event</literal>类中:
</para>
<programlisting><![CDATA[private Set participants = new HashSet();
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants = participants;
}]]></programlisting>
<para>
<literal>Event.hbm.xml</literal>里面也映射这个关联。
</para>
<programlisting><![CDATA[<set name="participants" table="PERSON_EVENT" inverse="true">
<key column="EVENT_ID"/>
<many-to-many column="PERSON_ID" class="events.Person"/>
</set>]]></programlisting>
<para>
如你所见,两个映射文件里都有普通的<literal>set</literal>映射。注意在两个映射文件中,互换了<literal>key</literal><literal>many-to-many</literal>的字段名。这里最重要的是<literal>Event</literal>映射文件里增加了<literal>set</literal>元素的<literal>inverse="true"</literal>属性。
</para>
<para>
这意味着在需要的时候Hibernate能在关联的另一端 <literal>Person</literal>类得到两个实体间关联的信息。这将会极大地帮助你理解双向关联是如何在两个实体间被创建的。
</para>
</sect2>
<sect2 id="tutorial-associations-usingbidir">
<title>
使双向连起来
</title>
<para>
首先请记住Hibernate并不影响通常的Java语义。 在单向关联的例子中,我们是怎样在<literal>Person</literal><literal>Event</literal>之间创建联系的?我们把<literal>Event</literal>实例添加到<literal>Person</literal>实例内的event引用集合里。因此很显然如果我们要让这个关联可以双向地工作我们需要在另外一端做同样的事情 <literal>Person</literal>实例加入<literal>Event</literal>类内的Person引用集合。这“在关联的两端设置联系”是完全必要的而且你都得这么做。
</para>
<para>
许多开发人员防御式地编程,创建管理关联的方法来保证正确的设置了关联的两端,比如在<literal>Person</literal>里:
</para>
<programlisting><![CDATA[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);
}]]></programlisting>
<para>
注意现在对于集合的get和set方法的访问级别是protected - 这允许在位于同一个包package中的类以及继承自这个类的子类可以访问这些方法但禁止其他任何人的直接访问避免了集合内容的混乱。你应尽可能地在另一端也把集合的访问级别设成protected。
</para>
<para>
<literal>inverse</literal>映射属性究竟表示什么呢对于你和Java来说一个双向关联仅仅是在两端简单地正确设置引用。然而Hibernate并没有足够的信息去正确地执行<literal>INSERT</literal><literal>UPDATE</literal>语句(以避免违反数据库约束),所以它需要一些帮助来正确的处理双向关联。把关联的一端设置为<literal>inverse</literal>将告诉Hibernate忽略关联的这一端把这端看成是另外一端的一个<emphasis>镜象mirror</emphasis>。这就是所需的全部信息Hibernate利用这些信息来处理把一个有向导航模型转移到数据库schema时的所有问题。你只需要记住这个直观的规则所有的双向关联需要有一端被设置为<literal>inverse</literal>。在一对多关联中它必须是代表多many的那端。而在多对多many-to-many关联中你可以任意选取一端因为两端之间并没有差别。
</para>
</sect2>
<para>
让我们把进入一个小型的web应用程序。
</para>
</sect1>
<sect1 id="tutorial-webapp">
<title>第三部分 - EventManager web应用程序</title>
<para>
Hibernate web应用程序使用<literal>Session</literal><literal>Transaction</literal>的方式几乎和独立应用程序是一样的。但是有一些常见的模式pattern非常有用。现在我们编写一个<literal>EventManagerServlet</literal>。这个servlet可以列出数据库中保存的所有的events还提供一个HTML表单来增加新的events。
</para>
<sect2 id="tutorial-webapp-servlet" revision="2">
<title>编写基本的servlet</title>
<para>
在你的源代码目录的<literal>events</literal>包中创建一个新的类:
</para>
<programlisting><![CDATA[package events;
// Imports
public class EventManagerServlet extends HttpServlet {
// Servlet code
}]]></programlisting>
<para>
我们后面会用到<literal>dateFormatter</literal> 的工具, 它把<literal>Date</literal>对象转换为字符串。只要一个formatter作为servlet的成员就可以了。
</para>
<para>
这个servlet只处理 HTTP <literal>GET</literal> 请求,因此,我们要实现的是<literal>doGet()</literal>方法:
</para>
<programlisting><![CDATA[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();
throw new ServletException(ex);
}
}]]></programlisting>
<para>
我们称这里应用的模式为每次请求一个session<emphasis>(session-per-request)</emphasis>。当有请求到达这个servlet的时候通过对<literal>SessionFactory</literal>的第一次调用打开一个新的Hibernate <literal>Session</literal>。然后启动一个数据库事务&mdash;所有的数据访问都是在事务中进行不管是读还是写我们在应用程序中不使用auto-commit模式
</para>
<para>
<emphasis>不要</emphasis>为每次数据库操作都使用一个新的Hibernate <literal>Session</literal>。将Hibernate <literal>Session</literal>的范围设置为整个请求。要用<literal>getCurrentSession()</literal>这样它自动会绑定到当前Java线程。
</para>
<para>
下一步对请求的可能动作进行处理渲染出反馈的HTML。我们很快就会涉及到那部分。
</para>
<para>
最后,当处理与渲染都结束的时候,这个工作单元就结束了。假若在处理或渲染的时候有任何错误发生,会抛出一个异常,回滚数据库事务。这样,<literal>session-per-request</literal>模式就完成了。为了避免在每个servlet中都编写事务边界界定的代码可以考虑写一个servlet 过滤器filter来更好地解决。关于这一模式的更多信息请参阅Hibernate网站和Wiki这一模式叫做<emphasis>Open Session in View</emphasis>&mdash;只要你考虑用JSP来渲染你的视图view而不是在servlet中你就会很快用到它。
</para>
</sect2>
<sect2 id="tutorial-webapp-processing" revision="1">
<title>处理与渲染</title>
<para>
我们来实现处理请求以及渲染页面的工作。
</para>
<programlisting><![CDATA[// 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();]]></programlisting>
<para>
<literal>listEvents()</literal>方法使用绑定到当前线程的Hibernate <literal>Session</literal>来执行查询:
</para>
<programlisting><![CDATA[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>");
for (Iterator it = result.iterator(); 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>");
}
}]]></programlisting>
<para>
最后,<literal>store</literal>动作会被导向到<literal>createAndStoreEvent()</literal>方法,它也使用当前线程的<literal>Session</literal>:
</para>
<programlisting><![CDATA[protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
HibernateUtil.getSessionFactory()
.getCurrentSession().save(theEvent);
}]]></programlisting>
<para>
大功告成这个servlet写完了。Hibernate会在单一的<literal>Session</literal><literal>Transaction</literal>中处理到达的servlet请求。如同在前面的独立应用程序中那样Hibernate可以自动的把这些对象绑定到当前运行的线程中。这给了你用任何你喜欢的方式来对代码分层及访问<literal>SessionFactory</literal>的自由。通常,你会用更加完备的设计,把数据访问代码转移到数据访问对象中(DAO模式。请参见Hibernate Wiki那里有更多的例子。
</para>
</sect2>
<sect2 id="tutorial-webapp-deploy">
<title>部署与测试</title>
<para>
要发布这个程序你得把它打成web发布包WAR文件。把下面的脚本加入到你的<literal>build.xml</literal>中:
</para>
<programlisting><![CDATA[<target name="war" depends="compile">
<war destfile="hibernate-tutorial.war" webxml="web.xml">
<lib dir="${librarydir}">
<exclude name="jsdk*.jar"/>
</lib>
<classes dir="${targetdir}"/>
</war>
</target>]]></programlisting>
<para>
这段代码在你的开发目录中创建一个<literal>hibernate-tutorial.war</literal>的文件。它把所有的类库和<literal>web.xml</literal>描述文件都打包进去web.xml 文件应该位于你的开发根目录中:
</para>
<programlisting><![CDATA[<?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>events.EventManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Event Manager</servlet-name>
<url-pattern>/eventmanager</url-pattern>
</servlet-mapping>
</web-app>]]></programlisting>
<para>
请注意在你编译和部署web应用程之前需要一个附加的类库<literal>jsdk.jar</literal>。这是Java Servlet开发包假若你还没有可以从Sun网站上下载把它copy到你的lib目录。但是它仅仅是在编译时需要不会被打入WAR包。
</para>
<para>
在你的开发目录中,调用<literal>ant war</literal>来构建、打包,然后把<literal>hibernate-tutorial.war</literal>文件拷贝到你的tomcat的<literal>webapps</literal>目录下。假若你还没安装Tomcat就去下载一个按照指南来安装。对此应用的发布你不需要修改任何Tomcat的配置。
</para>
<para>
在部署完启动Tomcat之后通过<literal>http://localhost:8080/hibernate-tutorial/eventmanager</literal>进行访问你的应用在第一次servlet 请求发生时请在Tomcat log中确认你看到Hibernate被初始化了<literal>HibernateUtil</literal>的静态初始化器被调用),假若有任何异常抛出,也可以看到详细的输出。
</para>
</sect2>
</sect1>
<sect1 id="tutorial-summary">
<title>
总结
</title>
<para>
本章覆盖了如何编写一个简单独立的Hibernate命令行应用程序及小型的Hibernate web应用程序的基本要素。
</para>
<para>
如果你已经对Hibernate感到自信通过开发指南目录继续浏览你感兴趣的内容那些会被问到的问题大多是事务处理 (<xref linkend="transactions"/>)抓取fetch的效率 (<xref linkend="performance"/>)或者API的使用 (<xref linkend="objectstate"/>)和查询的特性(<xref linkend="objectstate-querying"/>)。
</para>
<para>
别忘了去Hibernate的网站查看更多有针对性的示例。
</para>
</sect1>
</chapter>