hibernate-orm/reference/zh-cn/modules/quickstart.xml

461 lines
24 KiB
XML
Raw Normal View History

<chapter id="quickstart">
<title>在Tomcat中快速上手</title>
<sect1 id="quickstart-intro" revision="2">
<title>开始Hibernate之旅</title>
<para>
这份教程描述如何在Apache Tomcat servlet容器中为web应用程序配置Hibernate 3.0(我们使用Tomcat 4.1版本与5.0版本差别很小)。Hibernate在大多数主流J2EE应用服务器
的运行环境中都可以工作良好甚至也可以在独立Java应用程序中使用。在本教程中使用的示例数据库系统是PostgreSQL 7.4,只需要修改Hibernate SQL语言配置与连接属性就可以
很容易的支持其他数据库了。
</para>
<para>
第一步我们必须拷贝所有需要的库文件到Tomcat安装目录中。在这篇教程中我们使用一个独立的web Context配置<literal>webapps/quickstart</literal>)。我们确认全局库文件(<literal>TOMCAT/common/lib</literal>和本web应用程序上下文的路径对于jar来说是<literal>webapps/quickstart/WEB-INF/lib</literal>对于class文件来说是<literal>webapps/quickstart/WEB-INF/classes</literal>)能够被类装载器检索到。我们把这两个类装载器级别分别称做全局类路径(global classpath)和上下文类路径(context classpath)。
</para>
<para>
现在把这些库文件copy到两个类路径去:
</para>
<orderedlist>
<listitem>
<para>
把数据库需要的JDBC驱动文件拷贝到全局类路径这是tomcat捆绑的DBCP连接池所需要的。Hibernate使用JDBC连接数据库方式执行SQL语句所以你要么提供外部连接池中的连接给Hibernate或者配置Hibernate自带的连接池C3PO,Proxool。对于本教程来说<literal>pg74jdbc3.jar</literal>库文件支持PostgreSQL 7.4和JDK 1.4)到全局类装载路径下即可。如果你希望使用其他的数据库拷贝其相应的JDBC 驱动文件)。
</para>
</listitem>
<listitem>
<para>
永远不要拷贝任何其他东西到Tomcat的全局类路径下否则你可能在使用其他一些工具上遇到麻烦比如log4j, commons-logging等等。
一定要让每个web应用程序使用自己的上下文类路径就是说把你自己需要的类库拷贝到<literal>WEB-INF/lib</literal>下去把配置文件configuration/property等配置文件拷贝到<literal>WEB-INF/classes</literal>下面去。这两个目录都是当前程序缺省的上下文类路径。
</para>
</listitem>
<listitem>
<para>
Hibernate本身打包成一个JAR类库。将<literal>hibernate3.jar</literal>文件拷贝到程序的上下文类路径下和你应用程序的其他库文件放一起。在运行时Hibernate还需要一些第三方类库它们在Hibernate发行包的<literal>lib/</literal>目录下。参见<xref linkend="3rdpartylibs"/>。把所需要的第三方库文件也拷贝到上下文类路径下。
</para>
</listitem>
</orderedlist>
<table frame="topbot" id="3rdpartylibs">
<title>
Hibernate 第三方类库
</title>
<tgroup cols="2" rowsep="1" colsep="1">
<colspec colname="c1" colwidth="1*"/>
<colspec colname="c2" colwidth="2*"/>
<thead>
<row>
<entry align="center">
类库
</entry>
<entry align="center">
描述
</entry>
</row>
</thead>
<tbody>
<row>
<entry>
antlr (必需)
</entry>
<entry>
Hibernate使用ANTLR来产生查询分析器这个类库在运行环境下时也是必需的。
</entry>
</row>
<row>
<entry>
dom4j (必需)
</entry>
<entry>
Hibernate使用dom4j解析XML配置文件和XML映射元文件。
</entry>
</row>
<row>
<entry>
CGLIB ,asm(必需)
</entry>
<entry>
Hibernate在运行时使用这个代码生成库增强类与Java反射机制联合使用
</entry>
</row>
<row>
<entry>
Commons Collections, Commons Logging (必需)
</entry>
<entry>
Hibernat使用Apache Jakarta Commons项目提供的多个工具类库。
</entry>
</row>
<row>
<entry>
EHCache (必需)
</entry>
<entry>
Hibernate可以使用不同cache缓存工具作为二级缓存。EHCache是缺省的cache缓存工具。
</entry>
</row>
<row>
<entry>
Log4j (可选)
</entry>
<entry>
Hibernate使用Commons Logging API,它也可以依次使用Log4j作为底层实施log的机制。如果上下文类目录中存在Log4j库则Commons Logging使用Log4j和并它在上下文类路径中寻找的<literal>log4j.properties</literal>文件。你可以使用在Hibernate发行包中包含中的那个示例Log4j的配置文件。这样把log4j.jar和它的配置文件位于<literal>src/</literal>目录中)拷贝到你的上下文类路径下,就可以在后台看到底程序如何运行的。
</entry>
</row>
<row>
<entry>
其他文件是不是必需的?
</entry>
<entry>
请察看Hibernate发行包中的 lib/README.txt文件这是一个Hibernate发行包中附带的第三方类库的列表他们总是保持最新的。你可以在那里找到所有必需或者可选的类库(注意:其中的"buildtime required"指的是编译Hibernate时所需要而非编译你自己的程序所必需的类库
</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
接下来我们来配置在Tomcat和Hibernate中共用的数据库连接池。也就是说Tomcat会提供经过池处理的JDBC连接用它内置的DBCP连接池Hibernate通过JNDI方式来请求获得JDBC连接。作为替代方案你也可以让Hibernate自行管理连接池。Tomcat把连接池绑定到JNDI,我们要在Tomcat的主配置文件<literal>TOMCAT/conf/server.xml</literal>)中加一个资源声明:
</para>
<programlisting><![CDATA[<Context path="/quickstart" docBase="quickstart">
<Resource name="jdbc/quickstart" scope="Shareable" type="javax.sql.DataSource"/>
<ResourceParams name="jdbc/quickstart">
<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>
<!-- DBCP database connection settings -->
<parameter>
<name>url</name>
<value>jdbc:postgresql://localhost/quickstart</value>
</parameter>
<parameter>
<name>driverClassName</name><value>org.postgresql.Driver</value>
</parameter>
<parameter>
<name>username</name>
<value>quickstart</value>
</parameter>
<parameter>
<name>password</name>
<value>secret</value>
</parameter>
<!-- DBCP connection pooling options -->
<parameter>
<name>maxWait</name>
<value>3000</value>
</parameter>
<parameter>
<name>maxIdle</name>
<value>100</value>
</parameter>
<parameter>
<name>maxActive</name>
<value>10</value>
</parameter>
</ResourceParams>
</Context>]]></programlisting>
<para>
我们在这个例子中要配置的上下文叫做<literal>quickstart</literal>,它位于<literal>TOMCAT/webapp/quickstart</literal>目录下。如果要访问这个应用程序,在你的浏览器中输入<literal>http://localhost:8080/quickstart</literal>就可以了(当然,在后面加上在你的<literal>web.xml</literal>文件中配置好你的servlet)。你现在可以创建一个只含有空<literal>process()</literal>的简单servlet了。
</para>
<para>
Tomcat现在通过JNDI的方式<literal>java:comp/env/jdbc/quickstart</literal>来提供连接。如果你在配置连接池遇到问题请查阅Tomcat文档。如果你遇到了JDBC驱动所报的exception出错信息请在没有Hibernate的环境下先测试JDBC连接池本身是否配置正确。Tomcat和JDBC的配置教程可以在Web上查到。
</para>
<para>
下一步就是配置Hibernate。首先Hibernate必须知道它如何获得JDBC连接在这里我们使用基于XML格式的Hibernate配置文件。当然使用properties文件的进行配置但缺少一些XML语法的特性。这个XML配置文件必须放在上下文类路径(<literal>WEB-INF/classes</literal>)下面,命名为<literal>hibernate.cfg.xml</literal>:
</para>
<programlisting><![CDATA[<?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>
<session-factory>
<property name="connection.datasource">java:comp/env/jdbc/quickstart</property>
<property name="show_sql">false</property>
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<!-- Mapping files -->
<mapping resource="Cat.hbm.xml"/>
</session-factory>
</hibernate-configuration>]]></programlisting>
<para>
在这里我们关闭了SQL命令的log同时告诉Hibernate使用哪种SQL数据库用语(Dialet)以及如何得到JDBC连接通过Tomcat声明绑定的JNDI地址。Dialet是必需配置的因为不同的数据库都和"SQL标准"有一些出入。不用担心Hibernate会替你处理这些差异Hibernate支持所有主流的商业和开放源代码数据库。
</para>
<para>
<literal>SessionFactory</literal>是Hibernate的一个概念表示对应一个数据存储源。通过创建多个XML配置文件并在你的程序中创建多个<literal>Configuration</literal><literal>SessionFactory</literal>对象,就可以支持多个数据库了。
</para>
<para>
<literal>hibernate.cfg.xml</literal>中的最后一个元素声明了<literal>Cat.hbm.xml</literal>这是一个Hibernate XML映射文件对应于持久化类<literal>Cat</literal>。这个文件包含了把<literal>Cat</literal> POJO类映射到数据库表或多个数据库表的元数据。我们稍后就回来看这个文件。下一步让我们先编写这个POJO类然后在声明它的映射元数据。
</para>
</sect1>
<sect1 id="quickstart-persistentclass" revision="1">
<title>第一个持久化类</title>
<para>
Hibernate使用简单的Java对象(Plain Old Java Objects ,就是POJOs,有时候也称作Plain Ordinary Java Objects这种编程模型来进行持久化。一个POJO很像JavaBean,通过getter和setter方法访问其属性对外则隐藏了内部实现的细节(假若需要的话,Hibernate也可以直接访问其属性字段)。
</para>
<programlisting><![CDATA[package org.hibernate.examples.quickstart;
public class Cat {
private String id;
private String name;
private char sex;
private float weight;
public Cat() {
}
public String getId() {
return id;
}
private void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
}]]></programlisting>
<para>
Hibernate对属性使用的类型不加任何限制。所有的Java JDK类型和原始类型比如<literal>String</literal>,<literal>char</literal><literal>Date</literal>)都可以被映射也包括Java 集合Java collections framework)中的类。你可以把它们映射成为值,值集合,或者与其他实体类相关联。<literal>id</literal>是一个特殊的属性,代表了这个类的数据库标识符(主键),对于类似于<literal>Cat</literal>这样的实体类我们强烈建议使用。Hibernate也可以使用内部标识符但这样我们会失去一些程序架构方面的灵活性。
</para>
<para>
持久化类不需要实现什么特别的接口也不需要从一个特别的持久化根类继承下来。Hibernate也不需要使用任何编译期处理比如字节码增强操作它独立的使用Java反射机制和运行时类增强通过CGLIB。所以不依赖于Hibernate我们就可以把POJO的类映射成为数据库表。
</para>
</sect1>
<sect1 id="quickstart-mapping" revision="1">
<title>映射cat</title>
<para>
<literal>Cat.hbm.xml</literal>映射文件包含了对象/关系映射O/R Mapping所需的元数据。元数据包含持久化类的声明和属性到数据库的映射指向字段和其他实体的外键关联
</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>
<class name="org.hibernate.examples.quickstart.Cat" table="CAT">
<!-- A 32 hex character is our surrogate key. It's automatically
generated by Hibernate with the UUID pattern. -->
<id name="id" type="string" unsaved-value="null" >
<column name="CAT_ID" sql-type="char(32)" not-null="true"/>
<generator class="uuid.hex"/>
</id>
<!-- A cat has to have a name, but it shouldn' be too long. -->
<property name="name">
<column name="NAME" length="16" not-null="true"/>
</property>
<property name="sex"/>
<property name="weight"/>
</class>
</hibernate-mapping>]]></programlisting>
<para>
每个持久化类都应该有一个标识属性(实际上,这个类只代表实体,而不是独立的值类型类,后者会被映射称为实体对象中的一个组件)。这个属性用来区分持久化对象:如果<literal>catA.getId().equals(catB.getId())</literal>结果是true的话这两个Cat就是相同的。这个概念称为<emphasis>数据库标识</emphasis>。Hiernate附带了几种不同的标识符生成器用于不同的场合包括数据库本地的顺序(sequence)生成器、hi/lo高低位标识模式、和程序自己定义的标识符。我们在这里使用UUID生成器只在测试时建议使用如果使用数据库自己生成的整数类型的键值更好并指定<literal>CAT</literal>表中的<literal>CAT_ID</literal>字段(作为表的主键)存放生成的标识值。
</para>
<para>
<literal>Cat</literal>的其他属性都映射到同一个表的字段。对<literal>name</literal>属性来说我们把它显式地声明映射到一个数据库字段。如果数据库schema是通过由映射声明使用Hibernate的<emphasis>SchemaExport</emphasis>工具自动生成的作为SQL DDL指令的话这就特别有用。所有其它的属性都用Hibernate的默认值映射大多数情况你都会这样做。数据库中的<literal>CAT</literal>表看起来是这样的:
</para>
<programlisting><![CDATA[ Column | Type | Modifiers
--------+-----------------------+-----------
cat_id | character(32) | not null
name | character varying(16) | not null
sex | character(1) |
weight | real |
Indexes: cat_pkey primary key btree (cat_id)]]></programlisting>
<para>
你现在可以在你的数据库中手工创建这个表了,如果你需要使用<literal>hbm2ddl</literal>工具把这个步骤自动化,请参阅<xref linkend="toolsetguide"/>。这个工具能够创建完整的SQL DDL包括表定义自定义的字段类型约束惟一约束和索引。
</para>
</sect1>
<sect1 id="quickstart-playingwithcats" revision="2">
<title>与Cat同乐</title>
<para>
我们现在可以开始Hibernate的<literal>Session</literal>了。它是一个<emphasis>持久化管理器</emphasis>,我们通过它来从数据库中存取<literal>Cat</literal>。首先,我们要从<literal>SessionFactory</literal>中获取一个<literal>Session</literal>Hibernate的工作单元
</para>
<programlisting><![CDATA[SessionFactory sessionFactory =
new Configuration().configure().buildSessionFactory();]]></programlisting>
<para>
通过对<literal>configure()</literal>的调用来装载<literal>hibernate.cfg.xml</literal>配置文件,并初始化成一个<literal>Configuration</literal>实例。
在创建 <literal>SessionFactory</literal><emphasis>之前</emphasis>(它是不可变的),你可以访问<literal>Configuration</literal>来设置其他属性(甚至修改映射的元数据)。我们应该在哪儿创建<literal>SessionFactory</literal>,在我们的程序中又如何访问它呢?
<literal>SessionFactory</literal>通常只是被初始化一次,比如说通过一个<emphasis>load-on-startup</emphasis> servlet的来初始化。这意味着你不应该在serlvet中把它作为一个实例变量来持有而应该放在其他地方。进一步的说我们需要使用<emphasis>单例Singleton</emphasis>模式,我们才能更容易的在程序中访问<literal>SessionFactory</literal>。下面的方法就同时解决了两个问题:对<literal>SessionFactory</literal>的初始配置与便捷使用。
</para>
<para>
我们实现一个<literal>HibernateUtil</literal>辅助类:
</para>
<programlisting><![CDATA[import org.hibernate.*;
import org.hibernate.cfg.*;
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
log.error("Initial SessionFactory creation failed.", ex);
throw new ExceptionInInitializerError(ex);
}
}
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() {
Session s = (Session) session.get();
// Open a new Session, if this Thread has none yet
if (s == null) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() {
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}]]></programlisting>
<para>
这个类不但在它的静态初始器中使用了<literal>SessionFactory</literal>,还使用了一个<literal>ThreadLocal</literal>变量来保存<literal>Session</literal>做为当前工作线程。在你使用这个辅助类之前请确保你理解了thread-local变量这个Java概念。你可以在<literal>CaveatEmptor</literal>(http://caveatemptor.hibernate.org/)上找到一个更加复杂和强大的 <literal>HibernateUtil</literal>
</para>
<para>
<literal>SessionFactory</literal>是安全线程,可以由很多线程并发访问并获取到<literal>Sessions</literal>。单个<literal>Session</literal>不是安全线程对象,它只代表与数据库之间的一次操作。<literal>Session</literal>通过<literal>SessionFactory</literal>获得并在所有的工作完成后关闭。在你servlet的<literal>process()</literal>中可以象是这么写的(省略了异常情况处理):
</para>
<programlisting><![CDATA[Session session = HibernateUtil.currentSession();
Transaction tx= session.beginTransaction();
Cat princess = new Cat();
princess.setName("Princess");
princess.setSex('F');
princess.setWeight(7.4f);
session.save(princess);
tx.commit();
HibernateUtil.closeSession();]]></programlisting>
<para>
在一个<literal>Session</literal>中,每个数据库操作都是在一个事务(transaction)中进行的这样就可以隔离开不同的操作甚至包括只读操作。我们使用Hibernate的<literal>Transaction</literal> API来从底层的事务策略中本例中是JDBC事务脱身出来。这样我们就不需要更改任何源代码就可以把我们的程序部署到一个由容器管理事务的环境中去使用JTA
</para>
<para>
这样你就可以随心所欲的多次调用<literal>HibernateUtil.currentSession();</literal>,你每次都会得到同一个当前线程的<literal>Session</literal>。不管是在你的servlet代码中或者在servlet filter中还是在HTTP结果返回之前你都必须确保这个<literal>Session</literal>在你的数据库访问工作完成后关闭。这样做还有一个好处就是可以容易的使用延迟装载lazy initialization<literal>Session</literal>在渲染view层的时候仍然打开着的所以你在遍历当前对象图的时候可以装载所需的对象。
</para>
<para>
Hibernate有不同的方法用来从数据库中取回对象。最灵活的方式就是使用Hibernate查询语言(HQL),这是一种容易学习的语言是对SQL的面向对象的强大扩展。
</para>
<programlisting><![CDATA[Transaction tx= session.beginTransaction();
Query query = session.createQuery("select c from Cat as c where c.sex = :sex");
query.setCharacter("sex", 'F');
for (Iterator it = query.iterate(); it.hasNext();) {
Cat cat = (Cat) it.next();
out.println("Female Cat: " + cat.getName() );
}
tx.commit();]]></programlisting>
<para>
Hibernate也提供一种面向对象的<emphasis>按条件查询</emphasis>API可以执行简洁安全类型的查询。当然Hibernate在所有与数据库的交互中都使用<literal>PrepatedStatement</literal>和参数绑定。你也可以使用Hibernate的直接SQL查询特性或者在特殊情况下从<literal>Session</literal>获取一个原始的JDBC连接。
</para>
</sect1>
<sect1 id="quickstart-summary" revision="1">
<title>结语</title>
<para>
在这个短小的教程中我们对Hibernate浅尝即止。请注意我们没有在例子中包含任何servlet相关代码。你必须自行编写servlet并插入适合你的Hibernate代码。
</para>
<para>
请记住Hibernate作为一个数据库访问层是与你的程序紧密相关的。通常情况下所有其他层次都依赖持久机制。请确信你理解了这种设计的内涵。
</para>
<para>
若希望学习更复杂的例子,请参阅http://caveatemptor.hibernate.org/ 。在 http://www.hibernate.org/Documentation 也可以得到其他教程的链接。
</para>
</sect1>
</chapter>