Hibernate的体系结构

第 1 章 在Tomcat中快速上手

1.1. 开始Hibernate之旅

这份教程解释如何在Apache Tomcat servlet容器中为web程序安装Hibernate 2.1。Hibernate在大多数主流J2EE应用服务器 的受管理环境中都可以良好运作,甚至也可以在独立Java应用程序中运行。在本例中使用的示例数据库系统是PostgreSQL 7.3,当然也可以 很容易的支持其他数据库,只需要修改Hibernate SQL方言的配置。

第一步,我们必须拷贝所有需要的运行库到Tomcat去。在这篇教程中,我们使用一个单独的web程序(webapps/quickstart)。我们要考虑全局库文件搜索路径(TOMCAT/common/lib)和本web应用程序上下文的类装载器搜索路径(对于jar来说是webapps/quickstart/WEB-INF/lib,对于class文件来说是webapps/quickstart/WEB-INF/classes)。我们把这两个类装载器级别分别称为全局类路径(global classpath)和上下文类路径(context classpath)。

现在,把这些库文件copy到两个类路径去:

  1. 把数据库需要的JDBC驱动拷贝到全局类路径。这是tomcat附带的DBCP连接池软件所要求的。Hibernate使用JDBC连接来对数据库执行SQL,所以你要么提供外部连接池中的连接给Hibernate,或者配置Hibernate使用它自己直接支持的池(C3PO,Proxcool)。对于本教程来说,把pg73jdbc3.jar库文件(对应PostgreSQL 7.3和JDK 1.4)到全局类装载器路径去。如果你希望使用一个不同的数据库,拷贝相应的JDBC 驱动)。

  2. 不要拷贝任何其他东西到全局类装载器去。否则你可能在一些工具上遇到麻烦,比如log4j, commons-logging等。 记得要使用每个web应用程序自己的上下文类路径,就是说把你自己的类库拷贝到WEB-INF/lib下去,把配置文件configuration/property拷贝到WEB-INF/classes下面去。这两个目录默认都是上下文类路径级别的。

  3. Hibernate本身打包成一个JAR库。hibernate2.jar文件要和你应用程序的其他库文件一起放在上下文类路径中。在运行时,Hibernate还需要一些第三方库,它们在Hibernate发行包的lib/目录下。参见表 1.1 “ Hibernate 第三方库 ”。把你需要的第三方库文件也拷贝到上下文类路径去。

表 1.1.  Hibernate 第三方库

描述
dom4j (必需)Hibernate在解析XML配置和XML映射元文件时需要使用dom4j。
CGLIB (必需)Hibernate在运行时使用这个代码生成库强化类(与Java反射机制联合使用)。
Commons Collections, Commons Logging (必需)Hibernat使用Apache Jakarta Commons项目提供的多个工具类库。
ODMG4 (必需)Hibernate提供了一个可选的ODMG兼容持久化管理界面。如果你需要映射集合,你就需要这个类库,就算你不是为了使用ODMG API。我们在这个教程中没有使用集合映射,但不管怎样把这个JAR拷贝过去总是不错的。
EHCache (必需)Hibernate可以使用不同的第二级Cache方案。如果没有修改配置的话,EHCache提供默认的Cache。
Log4j (可选)Hibernate使用Commons Logging API,后者可以使用Log4j作为底层实施log的机制。如果上下文类目录中存在Log4j库,Commons Logging就会使用Log4j和它在上下文类路径中找到的log4j.properties文件。在Hibernate发行包中包含有一个示例的properties文件。所以,如果你想看看幕布之后到底发生了什么,也把log4j.jar拷贝到你的上下文类路径去吧(它位于src/目录中)。
其他文件是不是必需的?请察看Hibernate发行包中的/lib/README.txt文件。这是一个Hibernate发行包中附带的第三方类库的列表,总是保持更新。你可以在那里找到所有必需或者可选的类库的列表。

我们现在来在Tomcat和Hibernate中配置共享的数据库连接池。也就是说Tomcat会提供经过池处理的JDBC连接(用它自己内置的DBCP连接池功能),Hibernate通过JNDI来请求这些连接。Tomcat 把连接池绑定到JNDI,我们要在Tomcat的主配置文件(TOMCAT/conf/server.xml)中加一行资源声明:

<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>

这个例子中我们要配置的上下文叫做quickstart,它位于TOMCAT/webapp/quickstart目录。要访问任何Servlet,在你的浏览器中访问http://localhost:8080/quickstart就可以了(当然,在后面加上在你的web.xml文件中列出的servlet的名字)。你现在可以创建一个只有空的process()方法的简单servlet了。

Tomcat在这个配置下,使用DBCP连接池,通过JNDI位置:java:comp/env/jdbc/quickstart提供带有缓冲池的JDBCConnections。如果你在让连接池工作的时候遇到困难,请查阅Tomcat文档。如果你得到了JDBC驱动的exception信息,请先不要用Hibernate,测试JDBC连接池本身是否正确。Tomcat和JDBC的教程可以在Web上查到。

下一步是配置hibernate,来使用绑定到JNDI的连接池中提供的连接。我们使用XML格式的Hibernate配置。当然,使用properties文件的方式在功能上也是一样的,也不提供什么特别好处。我们用XML配置的原因,是因为一般会更方便。XML配置文件放在上下文类路径(WEB-INF/classes)下面,称为hibernate.cfg.xml:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration
    PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-2.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">net.sf.hibernate.dialect.PostgreSQLDialect</property>

        <!-- Mapping files -->
        <mapping resource="Cat.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

我们关闭了SQL命令的log,告诉Hibernate使用哪种SQL数据库方言(dialet),还有如何得到JDBC连接(通过Tomcat声明数据源池绑定的JNDI地址)。方言是必需的,因为不同的数据库都和SQL "标准"有一些出入。Hibernate会替你照管这些差异之处,发行包包含了所有主流的商业和开放源代码数据库的方言。

SessionFactory是Hibernate的概念,对应一个数据存储源,如果有多个数据库,可以创建多个XML配置文件,也在你的程序中创建多个ConfigurationSessionFactory对象。

hibernate.cfg.xml中的最后一个元素声明了Cat.hbm.xml是一个Hibernate XML映射文件,对应持久化类Cat。这个文件包含了把POJO类映射到数据库表(或多个数据库表)的元数据。我们稍后就回来看这个文件。让我们先编写这个POJO类,再在声明它的映射元数据。

1.2. 第一个可持久化类

Hibernate最好的使用方法是使用普通的Java对象(Plain Old Java Objects ,就是POJOs,有时候也称作Plain Ordinary Java Objects)这种编程模型来进行持久化。一个POJO很像JavaBean,属性通过getter和setter方法访问,对外隐藏了内部实现的细节。

package net.sf.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;
    }

}

Hibernate对属性使用的类型不加限制。所有的Java JDK类型和原始类型(比如String,charDate)都可以被映射,也包括Java 集合框架(Java collections framework)中的类。你可以把它们映射成为值,值集合,或者与其他实体相关联。id是一个特殊的属性,代表了这个类的数据库标识符(主键),对于类似于Cat这样的实体我们强烈建议使用。Hibernate可以只在内部使用标识符,但这样我们会失去一些程序结构方面的灵活性。

持久化类不需要实现什么特别的接口,也不需要从一个特别的持久化根类继承下来。Hibernate也不需要使用任何编译期处理,比如字节码增强操作,它独立的使用Java反射机制和运行时类增强(通过CGLIB)。所以,在Hibernate中,POJO的类不需要任何前提条件,我们就可以把它映射成为数据库表。

1.3. 映射cat

Cat.hbm.xml映射文件包含了对象/关系映射所需的元数据。元数据包含持久化类的声明和属性到数据库的映射(指向字段和其他实体的外键关联)。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
    PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>

    <class name="net.sf.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>

每个持久化类都应该需要一个标识属性(实际上,只是哪些代表实体的类,而不是代表值对象的类,后者会被映射称为实体对象中的一个组件)。这个属性用来区分持久化对象:如果catA.getId().equals(catB.getId())结果是true的话,两只猫就是相同的。这个概念称为数据库标识。Hiernate附带了几种不同的标识符生成器,用于不同的场合(包括数据库本地的顺序(sequence)生成器、hi/lo高低位标识模式、和程序自己定义的标识符)。我们在这里使用UUID生成器(只在测试时建议使用,如果使用数据库自己生成的整数类型的键值更好),并指定CAT表的CAT_ID字段(作为表的主键)存放生成的标识值。

Cat的其他属性都映射到同一个表。对name属性来说,我们把它显式地声明映射到一个数据库字段。如果数据库schema是由映射声明使用Hibernate的SchemaExport工具自动生成的(作为SQL DDL指令),这特别有用。所有其它的属性都用Hibernate的默认值映射,大多数情况你都会这样做。数据库中的CAT表看起来是这样的:

 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)

你现在可以在你的数据库中首先创建这个表了,如果你需要使用SchemaExport工具把这个步骤自动化,请参阅第 15 章 工具箱指南。这个工具能够创建完整的SQL DDL,包括表定义,自定义的字段类型约束,惟一约束和索引。

1.4. 与猫同乐

我们现在可以开始Hibernate的Session了。它是持久化管理器接口,我们用它来从数据库中存取Cat。首先,我们要从SessionFactory中获取一个Session(Hibernate的工作单元)。

SessionFactory sessionFactory =
            new Configuration().configure().buildSessionFactory();

SessionFactory负责一个数据库,也只对应一个XML配置文件(hibernate.cfg.xml)。通过在创建 SessionFactory之前(它是不可变的),你可以访问Configuration来设置其他属性(甚至修改映射的元数据)。我们应该在哪儿创建SessionFactory,在我们的程序中又如何访问它呢? SessionFactory通常只是被初始化一次,比如通过一个load-on-startup servlet。这意味着你不应该在serlvet中把它作为一个实例变量来持有,应该放在其他地方。更深入一些的说,我们需要某种单例(Singleton)模式,我们才能更容易的在程序中访问SessionFactory。下面的方法同时解决了两个问题:配置和容易地访问SessionFactory

我们实现一个HibernateUtil辅助类:

import net.sf.hibernate.*;
import net.sf.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) {
            log.error("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();
            session.set(s);
        }
        return s;
    }

    public static void closeSession() throws HibernateException {
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

这个类不但在它的静态属性中使用了SessionFactory,还使用了ThreadLocal来为当前工作线程保存Session。在你想用这个辅助类之前,请确保你理解了thread-local变量这个Java概念。

SessionFactory是线程安全的,很多线程可以同时访问它,获取SessionSession不是线程安全的,它代表与数据库之间的一次操作。Session通过SessionFactory打开,在所有的工作完成后,需要关闭:

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();

Session中,每个数据库操作都是在一个事务(transaction)中进行的,这样就可以隔离开不同的操作(甚至包括只读操作)。我们使用Hibernate的Transaction API来从底层的事务策略中(本例中是JDBC事务)脱身。这样,如果需要把我们的程序部署到一个由容器管理事务的环境中去(使用JTA),我们就不需要更改源代码。请注意,我们上面的例子没有处理任何异常。

也请注意,你可以随心所欲的多次调用HibernateUtil.currentSession();,你每次都会得到同一个当前线程的Session。你必须确保Session在你的数据库访问工作完成后关闭,不管是在你的servlet代码中,或者在servlet filter中,HTTP结果返回之前。后者的一个附带好处是,可以容易的使用延迟装载:Session在渲染view层的时候仍然打开着,所以你在遍历对象图的时候可以装载所需的对象。

Hibernate有不同的方法来从数据库中取回对象。最灵活的方式是使用Hibernate查询语言(HQL),这是一种容易学习的语言,是对SQL的面向对象的强大扩展。

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();

Hibernate也提供一种面向对象的按条件查询API,可以执行公式化的类型安全的查询。当然,Hibernate在所有与数据库的交互中都使用PrepatedStatement和参数绑定。你也可以使用Hibernate的直接SQL查询功能,或者在特殊情况下从Session获取一个原始的JDBC连接。

1.5. 结语

在这个短小的教程中,我们只描绘了Hibernate的基本面目。请注意我们没有在例子中包含servlet相关代码。你必须自行编写servlet,然后把你认为合适的Hibernate代码插入。

请记住Hibernate作为数据库访问层,是与你的程序紧密相关的。一般,所有其他层次都依赖持久机制。请确信你理解了这种设计的含义。


第 2 章 体系结构

2.1. 总览

对Hibernate非常高层的概览:

这幅图展示了Hibernate使用数据库和配置文件数据来为应用程序提供持久化服务(和持久化的对象)。

让我们更细致地观察一下运行时的体系结构。 挺不幸的,Hibernate是比较复杂的,提供了好几种不同的运行方式。我们展示一下两种极端情况。轻型体系中,应用程序自己提供JDBC连接,并且自行管理事务。这种方式使用了Hibernate API的一个最小子集。

全面解决体系中,对于应用程序来说,所有的底层JDBC/JTA API都被抽象了,Hibernate会替你照管所有的细节。

下面是图中一些对象的定义:

SessionFactory (net.sf.hibernate.SessionFactory)

对属于单一数据库的编译过的映射文件的一个线程安全的,不可变的缓存快照。它是Session的工厂,是ConnectionProvider的客户。可能持有一个可选的(第二级)数据缓存,可以在进程级别或集群级别保存可以在事物中重用的数据。

可能持有事务之间重用的数据的缓存。

会话,Session (net.sf.hibernate.Session)

单线程,生命期短促的对象,代表应用程序和持久化层之间的一次对话。封装了一个JDBC连接。也是Transaction的工厂。保存有必需的(第一级)持久化对象的缓存,用于遍历对象图,或者通过标识符查找对象。

持有持久化对象的缓存。

持久化对象(Persistent Object)及其集合(Collection)

生命期短促的单线程的对象,包含了持久化状态和商业功能。它们可能是普通的JavaBeans/POJOs,唯一特别的是他们现在从属于且仅从属于一个Session。一旦Session被关闭,他们都将从Session中取消联系,可以在任何程序层自由使用(比如,直接作为传送到表现层的DTO,数据传输对象)。

临时对象(Transient Object)及其集合(Collection)

目前没有从属于一个Session的持久化类的实例。他们可能是刚刚被程序实例化,还没有来得及被持久化,或者是被一个已经关闭的Session所实例化的。

事务,Transaction (net.sf.hibernate.Transaction)

(可选) 单线程,生命期短促的对象,应用程序用它来表示一批工作的原子操作。是底层的JDBC,JTA或者CORBA事务的抽象。一个Session某些情况下可能跨越多个Transaction 事务

ConnectionProvider (net.sf.hibernate.connection.ConnectionProvider)

(可选)JDBC连接的工厂和池。从底层的Datasource或者 DriverManager抽象而来。对应用程序不可见,但可以被开发者扩展/实现。

TransactionFactory (net.sf.hibernate.TransactionFactory)

(可选)事务实例的工厂。对应用程序不可见,但可以被开发者扩展/实现。

在上面的轻型结构中,程序没有使用Transaction / TransactionFactory 或者ConnectionProvider API,直接和JTA/JDBC对话了。

第 3 章 SessionFactory配置

因为Hibernate被设计为可以在许多不同环境下工作,所以它有很多配置参数。幸运的是,大部分都已经有默认值了,Hibernate发行包中还附带有示例的hibernate.properties文件,它演示了一些可变的参数。一般你只需要把这个文件放到你的classpath,配置一下即可。

3.1. 可编程配置方式

net.sf.hibernate.cfg.Configuration的一个实例代表了应用程序中所有的Java类到SQL数据库的映射的集合。Configuration用于构造一个(不可变的)SessionFactory。这些映射是从一些XML映射文件中编译得来的。

你可以得到一个Configuration的实例,直接实例化它即可。下面有一个例子,用来从两个XML配置文件(位于classpath)中的映射中初始化:

Configuration cfg = new Configuration()
    .addFile("Item.hbm.xml")
    .addFile("Bid.hbm.xml");

另外一个(某些时候更好的)方法是让Hibernate自行用getResourceAsStream()来装载映射文件。

Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class);

Hibernate 就会在classpath中寻找叫做/org/hibernate/autcion/Item.hbm.xml/org/hibernate/autcion/Bid.hbm.xml的映射文件。这种方法取消了所有对文件名的硬编码。

Configuration也可以指定一些可选的配置项:

Properties props = new Properties();
...
Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class)
    .setProperties(props);

Configuration是仅在配置期使用的对象,从第一个SessionFactory开始建立的时候,它就失效了。

3.2. 获取SessionFactory

当所有的映射都被Configuration解析之后,应用程序为了得到Session实例,必须先得到它的工厂。这个工厂应该是被应用程序的所有线程共享的:

SessionFactory sessions = cfg.buildSessionFactory();

当然,Hibernate并不禁止你的程序实例化多个SessionFactory。在你使用不止一个数据库的时候,这就有用了。

3.3. 用户自行提供JDBC连接

SessionFactory可以使用一个用户自行提供的JDBC连接来打开一个Session。这种设计可以让应用程序来自己管理JDBC连接:

java.sql.Connection conn = datasource.getConnection();
Session session = sessions.openSession(conn);

// do some data access work

应用程序必须小心,不能在同一个连接上打开两个并行的session!

3.4. Hibernate提供的JDBC连接

另一种方法就是,你可以让SessionFactory替你打开连接。SessionFactory必须事先知道JDBC连接的参数,有几种不同的方法设置参数:

  1. 传递一个java.util.PropertiesConfiguration.setProperties()方法。

  2. 在classpath的根目录中提供hibernate.properties文件。

  3. 通过java -Dproperty=value指定使用系统属性

  4. hibernate.cfg.xml文件中包含<property>元素。详情见后。

如果你使用这种方法,打开一个Session是非常简单的:

Session session = sessions.openSession(); // open a new Session
// do some data access work, a JDBC connection will be used on demand

所有的Hibernate属性名和约束都在net.sf.hibernate.cfg.Environment类中定义。我们讨论一下JDBC连接配置最重要的几项设置:

假若你设置了如下的属性,Hibernate会使用java.sql.DriverManager来得到连接,并建立连接池:

表 3.1. Hibernate JDBC属性

属性名用途
hibernate.connection.driver_classjdbc驱动类
hibernate.connection.urljdbc URL
hibernate.connection.username数据库用户名
hibernate.connection.password数据库用户密码
hibernate.connection.pool_size连接池容量最大数

Hibernate的连接池算法是非常可配置的。它的用途是让你上手,但是并非让你在生产系统中使用的,甚至不是用来做性能测试的。如果为了得到最好的性能和可靠性需要使用第三方的池,请用你连接池特定的设置来取代hibernate.connection.pool_size属性。

C3P0是随Hibernate发行包一起发布的一个开放源代码JDBC连接池,你可以在lib 目录中找到。假若你设置了hibernate.c3p0.* 属性,Hibernate会使用内置的C3P0ConnectionProvider作为连接池。 对Apache DBCP和Proxool的支持也是内置的。你必须设置hibernate.dbcp.*属性 (DBCP连接池属性)来打开DBCPConnectionProvider。如果打开hibernate.dbcp.ps.* (DBCP 语句缓存属性)可以使用Prepared statement缓存(高度推荐)。要知道它们的含义,请查阅Apache commons-pool的文档。如果你想要用Proxool,你需要设置hibernate.proxool.*系列属性。

下面是使用C3P0的一个例子:

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 = net.sf.hibernate.dialect.PostgreSQLDialect

在Application Server内使用时,Hibernate可以从JNDI中注册的javax.sql.Datasource取得连接。需要设置如下属性:

表 3.2. Hibernate 数据源(Datasource)属性

属性名用途
hibernate.connection.datasourcedatasource JNDI 名字
hibernate.jndi.urlJNDI 提供者的URL (可选)
hibernate.jndi.classJNDI InitialContextFactory的类名 (可选)
hibernate.connection.username数据库用户名 (可选)
hibernate.connection.password数据库密码 (可选)

下面是一个使用应用服务器提供的JNDI数据源的例子:

hibernate.connection.datasource = java:/comp/env/jdbc/MyDB
hibernate.transaction.factory_class = \
    net.sf.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
    net.sf.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = \
    net.sf.hibernate.dialect.PostgreSQLDialect

从JNDI数据源获得的JDBC连接自动具有应用服务器的容器管理事务的特性。

可以通过"hibernate.connnection"开头的属性来设置特别的连接属性。比如,你可以通过hibernate.connnection.charSet来指定charSet

通过实现net.sf.hibernate.connection.ConnectionProvider接口,你可以定义如何获得JDBC连接的策略。你可以通过设置hibernate.connection.provider_class来选择一个自定义的实现。

3.5. 可选配置属性

下面是一些在运行时可以改变Hibernate行为的其他配置。所有这些都是可选的,也有合理的默认值。

系统级别的配置只能通过java -Dproperty=value或者在hibernate.properties文件中配置,而不能通过传递给ConfigurationProperties实例来配置。

表 3.3. Hibernate配置属性

属性名用途
hibernate.dialectHibernate方言(Dialect)的类名 - 可以让Hibernate使用某些特定的数据库平台的特性

取值. full.classname.of.Dialect

hibernate.default_schema在生成的SQL中,scheml/tablespace的全限定名

取值. SCHEMA_NAME

hibernate.session_factory_name自动把创建的SessionFactory以这个名字绑定到JNDI中去.

取值. jndi/composite/name

hibernate.use_outer_join允许使用外连接抓取。已经失效。请使用max_fetch_depth

取值. true | false

hibernate.max_fetch_depth对单根联合(一对一,多对一),设置外连接抓取树的最大深度。如果是0将关闭默认的外连接抓取。

取值. 建议设置为03之间

hibernate.jdbc.fetch_size一个非零值,用来决定JDBC的获取量大小。(会调用Statement.setFetchSize()).
hibernate.jdbc.batch_size一个非零值,会开启Hibernate使用JDBC2的批量更新功能

取值. 建议值在 5 和 30之间。

hibernate.jdbc.use_scrollable_resultset允许Hibernate使用JDBC2提供的可滚动结果集。只有在使用用户自行提供的JDBC连接时,这个参数才是必需的。否则Hibernate会使用连接的元数据(metadata)。

取值. true | false

hibernate.jdbc.use_streams_for_binary在从JDBC读写binary(二进制)或者serializable(可序列化)类型时,是否使用stream(流). (这是一个系统级别的属性。)

取值. true | false

hibernate.jdbc.use_get_generated_keys允许使用JDBC3的PreparedStatement.getGeneratedKeys()在插入后获取数据库自身生成的key。需要JDBC3以上的驱动和JRE1.4以上,如果你的驱动和Hibernate关键字生成器一起使用有问题,请设为false。默认情况下,会用connection元数据根据驱动是否支持自动判断。

取值. true|false

hibernate.cglib.use_reflection_optimizer是否使用CGLIB来代替运行时反射操作。(系统级别属性,默认为在可能时都使用CGLIB).在调试的时候有时候使用反射会有用。

取值. true | false

hibernate.jndi.<propertyName>propertyName这个属性传递到JNDI InitialContextFactory去 (可选)
hibernate.connection.isolation事务隔离级别 (可选).请检查java.sql.Connection来得到取值的具体意义。注意大多数数据库不会支持所有的隔离级别。

取值. 1, 2, 4, 8

hibernate.connection.<propertyName>把 propertyName这个JDBC 属性传递到DriverManager.getConnection()
hibernate.connection.provider_class指定一个自定义的ConnectionProvider类名

取值. classname.of.ConnectionProvider

hibernate.cache.provider_class指定一个自定义的CacheProvider缓存提供者的类名

取值. classname.of.CacheProvider

hibernate.cache.use_minimal_puts优化第二级缓存操作,减少写操作,代价是读操作更频繁(对于集群缓存很有用)

取值. true|false

hibernate.cache.use_query_cache打开查询缓存,每个查询仍然必须指明cacheable。

取值. true|false

hibernate.cache.region_prefix用于第二级缓存区域名字的前缀

取值. prefix

hibernate.transaction.factory_class指定一个自定义的TransactionFactory类名,Hibernate Transaction API将会使用(默认是JDBCTransactionFactory)。

取值. classname.of.TransactionFactory

jta.UserTransactionJTATransactionFactory 用来从应用服务器获取JTA UserTransaction的JNDI名

取值. jndi/composite/name

hibernate.transaction.manager_lookup_classTransactionManagerLookup的类名 - 当在JTA环境中,JVM级别的缓存被打开的时候使用.

取值. classname.of.TransactionManagerLookup

hibernate.query.substitutions把Hibernate查询中的一些短语替换为SQL短语(比如说短语可能是函数或者字符)。

取值. hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC

hibernate.show_sql把所有的SQL语句都输出到控制台

取值. true | false

hibernate.hbm2ddl.autoSessionFactory创建后,自动输出schema创建DDL语句到数据库.和create-drop同时使用的话,数据库schema会在SessionFactory显式关闭后被drop掉。

取值. update | create | create-drop

3.5.1. SQL Dialects SQL 方言

你总是可以为你的数据库设置一个hibernate.dialect方言,它是net.sf.hibernate.dialect.Dialect 的一个子类。如果你不需要使用基于native或者sequence的主键自动生成算法,或者悲观锁定(使用Session.lock() 或 Query.setLockMode())的话,方言就可以不必指定。然而,假若你指定了一个方言,Hibernate会为上面列出的一些属性使用特殊默认值,省得你手工指定它们。

表 3.4. Hibernate SQL 方言 (hibernate.dialect)

RDBMS方言
DB2net.sf.hibernate.dialect.DB2Dialect
DB2 AS/400net.sf.hibernate.dialect.DB2400Dialect
DB2 OS390net.sf.hibernate.dialect.DB2390Dialect
PostgreSQLnet.sf.hibernate.dialect.PostgreSQLDialect
MySQLnet.sf.hibernate.dialect.MySQLDialect
Oracle (any version)net.sf.hibernate.dialect.OracleDialect
Oracle 9net.sf.hibernate.dialect.Oracle9Dialect
Sybasenet.sf.hibernate.dialect.SybaseDialect
Sybase Anywherenet.sf.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Servernet.sf.hibernate.dialect.SQLServerDialect
SAP DBnet.sf.hibernate.dialect.SAPDBDialect
Informixnet.sf.hibernate.dialect.InformixDialect
HypersonicSQLnet.sf.hibernate.dialect.HSQLDialect
Ingresnet.sf.hibernate.dialect.IngresDialect
Progressnet.sf.hibernate.dialect.ProgressDialect
Mckoi SQLnet.sf.hibernate.dialect.MckoiDialect
Interbasenet.sf.hibernate.dialect.InterbaseDialect
Pointbasenet.sf.hibernate.dialect.PointbaseDialect
FrontBasenet.sf.hibernate.dialect.FrontbaseDialect
Firebirdnet.sf.hibernate.dialect.FirebirdDialect

3.5.2. 外连接抓取(Outer Join Fetching )

如果你的数据库支持ANSI或者Oracle风格的外连接,外连接抓取可能提高性能,因为可以限制和数据库交互的数量(代价是数据库自身进行了更多的工作)。外连接抓取允许你在一个select语句中就可以得到一个由多对一或者一对一连接构成的对象图。

默认情况下,抓取在叶对象,拥有代理的对象或者产生对自身的引用时终止。

对一个特定关联来说,通过在XML映射文件中设置outer-join属性可以控制是否开启抓取功能。

也可以设置hibernate.max_fetch_depth0全局关闭此功能。如果设置为1或更高的数值,对所有的一对一和多对一关联会打开外连接抓取。默认情况下,它被设置为auto,即自动外连接。但是,一对多关联和集合永远不会使用外连接抓取,除非对每个特定的关联进行明确声明。这一行为可以在运行时通过Hibernate 查询重载。

3.5.3. 二进制流

Oracle限制通过它的JDBC驱动传递的byte数组的大小。如果你希望使用很大数量的binary或者serializable 类型的话,你需要打开hibernate.jdbc.use_streams_for_binary这只能通过JVM级别设定

3.5.4. 自定义CacheProvider

通过实现net.sf.hibernate.cache.CacheProvider接口,你可以整合一个JVM级别(或者集群的)第二级缓存进来。你可以通过hibernate.cache.provider_class选择某个自定义的实现。

3.5.5. 事务策略配置

如果你希望使用Hibernate的Transaction API,你必须通过hibernate.transaction.factory_class属性指定一个Transaction实例的工厂类。 Transaction API隐藏了底层的事务机制,允许Hibernate代码在受管制和非受管制的环境下都可以运行。

内置的两个标准选择是:

net.sf.hibernate.transaction.JDBCTransactionFactory

使用数据库(JDBC)事务(默认)

net.sf.hibernate.transaction.JTATransactionFactory

使用JTA(假若已经存在一个事务,Session会在这个上下文中工作,否则会启动一个新的事务。)

你也可以自行定义你的事务策略(比如说,一个CORBA事务服务)。

如果你希望在JTA环境中为可变数据使用JVM级别的缓存,你必须指定一个获取JTA TransactionManager的策略,但这对J2EE容易来说不是标准化的:

表 3.5. JTA TransactionManagers

事务工厂类Application Server
net.sf.hibernate.transaction.JBossTransactionManagerLookupJBoss
net.sf.hibernate.transaction.WeblogicTransactionManagerLookupWeblogic
net.sf.hibernate.transaction.WebSphereTransactionManagerLookupWebSphere
net.sf.hibernate.transaction.OrionTransactionManagerLookupOrion
net.sf.hibernate.transaction.ResinTransactionManagerLookupResin
net.sf.hibernate.transaction.JOTMTransactionManagerLookupJOTM
net.sf.hibernate.transaction.JOnASTransactionManagerLookupJOnAS
net.sf.hibernate.transaction.JRun4TransactionManagerLookupJRun4
net.sf.hibernate.transaction.BESTransactionManagerLookupBorland ES

3.5.6. 绑定SessionFactory到JNDI

绑定到JNDI的Hibernate SessionFactory可以简化查找工厂和创建新Session的过程。

假若你希望把SessionFactory绑定到一个JNDI命名空间,用hibernate.session_factory_name这个属性指定一个名字(比如,java:comp/env/hibernate/SessionFactory)。如果这个属性省略了,SessionFactory不会被绑定到JNDI。(在一个只读的JNDI默认值实现的环境中,这特别有用。比如,Tomcat。)

当把SessionFactory 绑定到JNDI,Hibernate会使用hibernate.jndi.url,hibernate.jndi.class的值来获得一个初始化上下文的实例。如果他们没有指定,就会使用默认的InitialContext

如果你选择使用JNDI,EJB或者其他工具类就可以通过JNDI查询得到SessionFactory

3.5.7. 查询语言替换

你可以使用hibernate.query.substitutions定义新的Hibernate查询短语。比如说:

hibernate.query.substitutions true=1, false=0

会在生成的SQL中把短语true和 false替换成整数值。

hibernate.query.substitutions toLowercase=LOWER

这可以让你重新命名SQL的LOWER函数。

3.6. Logging

通过Apache commons-logging,Hibernate记录很多事件。

commons-logging服务会直接输出到Apache log4j(如果你把log4j.jar放在你的classpath里),或者JDK1.4 logging(如果你运行JDK 1.4或以上版本)。你可以从http://jakarta.apache.org下载log4j。要使用log4j,你需要在你的classpath中放置一个log4j.properties文件。Hibernate发行包中包含一个示例的properties配置文件。

我们强烈建议你熟悉Hibernate的log信息。Hibernate的很多工作都会尽量详细的留下log,也没有让它变的难以阅读。这是用来解决问题的最基本的设施。当然也别忘了可以如前所述打开SQL 记录(hibernate.show_sql),在你要解决性能问题时,这是你第一步就需要做的。

3.7. 实现NamingStrategy(命名策略)

net.sf.hibernate.cfg.NamingStrategy接口允许你对数据库对象和schema元素指定“命名标准”。

你可以定义从Java标识符自动生成数据库标识符的规则,或者是映射文件中给出的“逻辑”字段名和表名处理为“物理”表名和字段名的规则。这个功能可以让映射文件变得简洁,消除无用的噪音(比如TBL_前缀等)。Hibernate使用的默认策略是几乎什么都不做。

你可以在增加映射(add mappings)之前调用Configuration.setNamingStrategy()来指定不同的策略。

SessionFactory sf = new Configuration()
    .setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
    .addFile("Item.hbm.xml")
    .addFile("Bid.hbm.xml")
    .buildSessionFactory();

net.sf.hibernate.cfg.ImprovedNamingStrategy 是一个内置的策略,对某些程序,你可以把它作为改造的起点。

3.8. XML配置文件

另一种配置属性的方法是把所有的配置都放在一个名为hibernate.cfg.xml的文件中。这个文件可以被用于替代hibernate.properties文件,如果二者都出现,它会覆盖properties文件。

XML配置文件默认会期望在CLASSPATH的根目录中找到。下面是一个例子。

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 2.0//EN"

 "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>

    <!-- a SessionFactory instance listed as /jndi/name -->
    <session-factory
        name="java:comp/env/hibernate/SessionFactory">

        <!-- properties -->
        <property name="connection.datasource">my/first/datasource</property>
        <property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">false</property>
        <property name="use_outer_join">true</property>
        <property name="transaction.factory_class">
            net.sf.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"/>

    </session-factory>

</hibernate-configuration>

配置Hibernate只需如此简单:

SessionFactory sf = new Configuration().configure().buildSessionFactory();

你可以使用另外一个名字的XML配置文件:

SessionFactory sf = new Configuration()
    .configure("catdb.cfg.xml")
    .buildSessionFactory();

第 4 章 持久化类(Persistent Classes)

持久化类是应用程序用来解决商业问题的类(比如,在电子交易程序中的Customer和Order)。持久化类,就如同它的名字暗示的,是短暂存在的,它的实例会被持久性保存于数据库中。

如果这些类符合简单的规则,Hibernate能够工作得最好,这些规则就是Plain Old Java Object (POJO,简单传统Java对象)编程模型。

4.1. POJO简单示例

大多数java程序需要一个持久化类的表示方法。

package eg;
import java.util.Set;
import java.util.Date;

public class Cat {
    private Long id; // identifier
    private String name;
    private Date birthdate;
    private Cat mate;
    private Set kittens
    private Color color;
    private char sex;
    private float weight;

    private void setId(Long id) {
        this.id=id;
    }
    public Long getId() {
        return id;
    }

    void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }

    void setMate(Cat mate) {
        this.mate = mate;
    }
    public Cat getMate() {
        return mate;
    }

    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 setKittens(Set kittens) {
        this.kittens = kittens;
    }
    public Set getKittens() {
        return kittens;
    }
    // addKitten not needed by Hibernate
    public void addKitten(Cat kitten) {
        kittens.add(kitten);
    }
    void setSex(char sex) {
        this.sex=sex;
    }
    public char getSex() {
        return sex;
    }
}

有四条主要的规则:

4.1.1. 为持久化字段声明访问器(accessors)和是否可变的标志(mutators)

Cat为它的所有可持久化字段声明了访问方法。很多其他ORM工具直接对实例变量进行持久化。我们相信在持久化机制中不限定这种实现细节,感觉要好得多。Hibernate对JavaBeans风格的属性实行持久化,采用如下格式来辨认方法:getFooisFoo 和 setFoo

属性不一定需要声明为public的。Hibernate可以对default,protected或者private的get/set方法对的属性一视同仁地执行持久化。

4.1.2. 实现一个默认的构造方法(constructor)

Cat有一个显式的无参数默认构造方法。所有的持久化类都必须具有一个默认的构造方法(可以不是public的),这样的话Hibernate就可以使用Constructor.newInstance()来实例化它们。

4.1.3. 提供一个标识属性(identifier property)(可选)

Cat有一个属性叫做id。这个属性包含了数据库表中的主关键字字段。这个属性可以叫任何名字,其类型可以是任何的原始类型、原始类型的包装类型、java.lang.String 或者是java.util.Date。(如果你的老式数据库表有联合主键,你甚至可以用一个用户自定义的类,其中每个属性都是这些类型之一。参见后面的关于联合标识符的章节。)

用于标识的属性是可选的。你可以不管它,让Hibernate内部来追踪对象的识别。当然,对于大多数应用程序来说,这是一个好的(也是很流行的)设计决定。

更进一步,一些功能只能对声明了标识属性的类起作用:

  • 级联更新(Cascaded updates)(参阅“自管理生命周期的对象(Lifecycle Objects)”)

  • Session.saveOrUpdate()

我们建议你对所有的持久化类采取同样的名字作为标识属性。更进一步,我们建议你使用一个可以为空(也就是说,不是原始类型)的类型。

4.1.4. 建议使用不是final的类 (可选)

Hibernate的关键功能之一,代理(proxies),要求持久化类不是final的,或者是一个全部方法都是public的接口的具体实现。

你可以对一个final的,也没有实现接口的类执行持久化,但是不能对它们使用代理——多多少少会影响你进行性能优化的选择。

4.2. 实现继承(Inheritance)

子类也必须遵守第一条和第二条规则。它从Cat继承了标识属性。

package eg;

public class DomesticCat extends Cat {
        private String name;

        public String getName() {
                return name;
        }
        protected void setName(String name) {
                this.name=name;
        }
}

4.3. 实现equals()hashCode()

如果你需要混合使用持久化类(比如,在一个Set中),你必须重载equals() 和 hashCode()方法。

这仅适用于那些在两个不同的Session中装载的对象,Hibernate在单个Session中仅保证JVM 辨别( a == b equals()的默认实现)!

就算两个对象ab实际是同一行数据库内容(它们拥有同样的主键值作为辨识符),我们也不能保证在特定的Session 之外它们是同一个Java实例。

最显而易见的实现equals()/hashCode()方法的办法就是比较两个对象的标识值。如果这个值是同堂的,他们必定是直线同一条数据库行,所以它们是相等的(如果都被加入到Set,在Set中只应该出现一个元素)。不幸的是,我们不能使用这种办法。Hibernate只会对已经持久化的对象赋予标识值,新创建的实例将不会有任何标识符值!我们推荐使用商业关键字相等原则来实现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 (!getName().equals(cat.getName())) return false;
        if (!getBirthday().equals(cat.getBirthday())) return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = getName().hashCode();
        result = 29 * result + getBirthday().hashCode();
        return result;
    }

}

记住我们的候选关键字(这个例子中是名字和生日的组合)只在特定的比较操作中有效(可能只在一个用例中)。我们不需要我们通常用于正式主键那么严格稳定的条件。

4.4. 持久化生命周期(Lifecycle)中的回调(Callbacks)

作为一个可选的步骤,可持久化类可以实现Lifecycle接口,它可以提供一些用于回调的方法,可以让持久化对象在save或load之后,或者在delete或update之前进行必要的初始化与清除步骤。

Hibernate Interceptor(拦截器)还提供了一种较少干扰的替代方法。

public interface Lifecycle {
        public boolean onSave(Session s) throws CallbackException;   (1)
        public boolean onUpdate(Session s) throws CallbackException; (2)
        public boolean onDelete(Session s) throws CallbackException; (3)
        public void onLoad(Session s, Serializable id);              (4)
}
(1)

onSave - 在对象即将被save或者insert的时候回调

(2)

onUpdate - 在对象即将被update的时候回调(也就是对象被传递给Session.update()的时候)

(3)

onDelete - 在对象即将被delete(删除)的时候回调

(4)

onLoad - 在对象刚刚被load(装载)后的时候回调

onSave()onDelete() 和 onUpdate() 可以被用来级联保存或者删除依赖的对象。这种做法是在映射文件中声明级联操作外的另外一种选择。onLoad()可以用来让对象从其持久化(当前)状态中初始化某些暂时的属性。不能用这种方式来装载依赖的对象,因为可能无法在此方法内部调用Session接口。 onLoad()onSave()和 onUpdate()另一种用法是用来在当前Session中保存一个引用,已备后用。

请注意onUpdate()并不是在每次对象的持久化状态被更新的时候就被调用的。它只在处于尚未被持久化的对象被传递给Session.update()的时候才会被调用。

如果onSave()onUpdate() 或者 onDelete()返回true,那么操作就被悄悄地取消了。如果其中抛出了CallbackException异常,操作被取消,这个异常会被继续传递给应用程序。

请注意onSave()是在标识符已经被赋予对象后调用的,除非是使用本地(native)方式生成关键字的。

4.5. 合法性检查(Validatable)回调

如果持久化类需要在保存其持久化状态前进行合法性检查,它可以实现下面的接口:

public interface Validatable {
        public void validate() throws ValidationFailure;
}

如果发现对象违反了某条规则,应该抛出一个ValidationFailure异常。在Validatable实例的validate()方法内部不应该改变它的状态。

Lifecycle接口的回调方法不同,validate()可能在任何时间被调用。应用程序不应该把validate()调用和商业功能联系起来。

4.6. XDoclet标记示例

下一章中我们将会展示Hibernate映射是如何用简单的,可阅读的XML格式表达的。很多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 mate;
    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="MATE_ID"
     */
    public Cat getMate() {
        return mate;
    }
    void setMate(Cat mate) {
        this.mate = mate;
    }

    /**
     * @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
     *  lazy="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;
    }
}

第 5 章 O/R Mapping基础

5.1. 映射声明(Mapping declaration)

对象和关系数据库之间的映射是用一个XML文档(XML document)来定义的。这个映射文档被设计为易读的,并且可以手工修改。映射语言是以Java为中心的,意味着映射是按照持久化类的定义来创建的,而非表的定义。

请注意,虽然很多Hibernate用户选择手工定义XML映射文档,也有一些工具来生成映射文档,包括XDoclet,Middlegen和AndroMDA.

让我们从一个映射的例子开始:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="eg">

        <class name="Cat" table="CATS" discriminator-value="C">
                <id name="id" column="uid" type="long">
                        <generator class="hilo"/>
                </id>
                <discriminator column="subclass" type="character"/>
                <property name="birthdate" type="date"/>
                <property name="color" not-null="true"/>
                <property name="sex" not-null="true" update="false"/>
                <property name="weight"/>
                <many-to-one name="mate" column="mate_id"/>
                <set name="kittens">
                        <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 属性。)

5.1.1. Doctype

所有的XML映射都需要定义如上所示的doctype。DTD可以从上述URL中获取,或者在hibernate-x.x.x/src/net/sf/hibernate目录中,或hibernate.jar文件中找到。Hibernate总是会在它的classptah中首先搜索DTD文件。

5.1.2. hibernate-mapping

这个元素包括三个可选的属性。schema属性,指明了这个映射所引用的表所在的schema名称。假若指定了这个属性,表名会加上所指定的schema的名字扩展为全限定名。假若没有指定,表名就不会使用全限定名。default-cascade指定了未明确注明cascade属性的Java属性和集合类Java会采取什么样的默认级联风格。auto-import属性默认让我们在查询语言中可以使用非全限定名的类名。

<hibernate-mapping
         schema="schemaName"                          (1)
         default-cascade="none|save-update"           (2)
         auto-import="true|false"                     (3)
         package="package.name"                       (4)
 />
(1)

schema (可选): 数据库schema名称。

(2)

default-cascade (可选 - 默认为 none): 默认的级联风格。

(3)

auto-import (可选 - 默认为 true): 指定是否我们可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。

(4)

package (可选): 指定一个包前缀,如果在映射文档中没有指定全限定名,就使用这个包名。

假若你有两个持久化类,它们的非全限定名是一样的(就是在不同的包里面--译者注),你应该设置auto-import="false"。假若说你把一个“import过”的名字同时对应两个类, Hibernate会抛出一个异常。

5.1.3. class

你可以使用class元素来定义一个持久化类:

<class
        name="ClassName"                              (1)
        table="tableName"                             (2)
        discriminator-value="discriminator_value"     (3)
        mutable="true|false"                          (4)
        schema="owner"                                (5)
        proxy="ProxyInterface"                        (6)
        dynamic-update="true|false"                   (7)
        dynamic-insert="true|false"                   (8)
        select-before-update="true|false"             (9)
        polymorphism="implicit|explicit"              (10)
        where="arbitrary sql where condition"         (11)
        persister="PersisterClass"                    (12)
        batch-size="N"                                (13)
        optimistic-lock="none|version|dirty|all"      (14)
        lazy="true|false"                             (15)
/>
(1)

name: 持久化类(或者接口)的Java全限定名。

(2)

table: 对应的数据库表名。

(3)

discriminator-value(辨别值) (可选 - 默认和类名一样):一个用于区分不同的子类的值,在多态行为时使用。

(4)

mutable(可变) (可选, 默认值为 true): 表明该类的实例可变(不可变)。

(5)

schema (可选): 覆盖在根<hibernate-mapping>元素中指定的schema名字。

(6)

proxy (可选): 指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。

(7)

dynamic-update(动态更新) (可选,默认为false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。

(8)

dynamic-insert(动态插入) (可选, 默认为false): 指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。

(9)

select-before-update (可选,默认值为false): 指定Hibernate除非确定对象的确被修改了,不会执行SQL UPDATE操作。在特定场合(实际上,只会发生在一个临时对象关联到一个新的session中去,执行update()的时候),这说明Hibernate会在UPDATE之前执行一次额外的SQL SELECT操作,来决定是否应该进行UPDATE

(10)

polymorphism(多形,多态) (可选, 默认值为 implicit (隐式)): 界定是隐式还是显式的使用查询多态。

(11)

where (可选) 指定一个附加的SQLWHERE 条件,在抓取这个类的对象时会一直增加这个条件。

(12)

persister (可选): 指定一个定制的ClassPersister

(13)

batch-size (可选,默认是1) 指定一个用于根据标识符抓取实例时使用的"batch size"(批次抓取数量)。

(14)

optimistic-lock(乐观锁定) (可选,默认是version): 决定乐观锁定的策略。

(15)

lazy(延迟) (可选): 假若设置 lazy="true",就是设置这个类自己的名字作为proxy接口的一种等价快捷形式。

若指明的持久化类实际上是一个接口,也可以被完美地接受。其后你可以用<subclass>来指定该接口的实际实现类名。你可以持久化任何static(静态的)内部类。记得应该使用标准的类名格式,就是说比如:Foo$Bar

不可变类,mutable="false"不可以被应用程序更新或者删除。这可以让Hibernate做一些小小的性能优化。

可选的proxy属性可以允许延迟加载类的持久化实例。Hibernate开始会返回实现了这个命名接口的CGLIB代理。当代理的某个方法被实际调用的时候,真实的持久化对象才会被装载。参见下面的“用于延迟装载的代理”。

Implicit (隐式)的多态是指,如果查询中给出的是任何超类、该类实现的接口或者该类的名字,都会返回这个类的实例;如果查询中给出的是子类的名字,则会返回子类的实例。 Explicit(显式)的多态是指,只有在查询中给出的明确是该类的名字时才会返回这个类的实例;同时只有当在这个<class>的定义中作为<subclass>或者<joined-subclass>出现的子类,才会可能返回。 大多数情况下,默认的polymorphism="implicit"都是合适的。 显式的多态在有两个不同的类映射到同一个表的时候很有用。(允许一个“轻型”的类,只包含部分表字段)。

persister属性可以让你定制这个类使用的持久化策略。你可以指定你自己实现的net.sf.hibernate.persister.EntityPersister的子类,你甚至可以完全从头开始编写一个net.sf.hibernate.persister.ClassPersister接口的实现,可能是用储存过程调用、序列化到文件或者LDAP数据库来实现的。参阅net.sf.hibernate.test.CustomPersister,这是一个简单的例子(“持久化”到一个Hashtable)。

请注意dynamic-updatedynamic-insert的设置并不会继承到子类,所以在<subclass>或者<joined-subclass>元素中可能需要再次设置。这些设置是否能够提高效率要视情形而定。请用你的智慧决定是否使用。

使用select-before-update通常会降低性能.当是在防止数据库不必要的触发update触发器,这就很有用了。

如果你打开了dynamic-update,你可以选择几种乐观锁定的策略:

  • version(版本检查) 检查version/timestamp字段

  • all(全部) 检查全部字段

  • dirty(脏检查)只检察修改过的字段

  • none(不检查)不使用乐观锁定

我们非常强烈建议你在Hibernate中使用version/timestamp字段来进行乐观锁定。对性能来说,这是最好的选择,并且这也是唯一能够处理在session外进行操作的策略(就是说,当使用Session.update()的时候)。 记住version或timestamp属性永远不能使null,不管何种unsaved-value策略,否则实例会被认为是尚未被持久化的。

5.1.4. id

被映射的类必须声明对应数据库表主键字段。大多数类有一个JavaBeans风格的属性,为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。

<id
        name="propertyName"                      (1)
        type="typename"                          (2)
        column="column_name"                     (3)
        unsaved-value="any|none|null|id_value"   (4)
        access="field|property|ClassName">       (5)

        <generator class="generatorClass"/>
</id>
(1)

name (可选): 标识属性的名字。

(2)

type (可选): 标识Hibernate类型的名字。

(3)

column (可选 - 默认为属性名): 主键字段的名字。

(4)

unsaved-value (可选 - 默认为null): 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。这可以把这种实例和从以前的session中装载过(可能又做过修改--译者注)但未再次持久化的实例区分开来。

(5)

access (可选 - 默认为property): Hibernate用来访问属性值的策略。

如果 name属性不存在,会认为这个类没有标识属性。

unsaved-value 属性很重要!如果你的类的标识属性不是默认为null的,你应该指定正确的默认值。

还有一个另外的<composite-id>声明可以访问旧式的多主键数据。我们强烈不鼓励使用这种方式。

5.1.4.1. generator

必须声明的<generator>子元素是一个Java类的名字,用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数,用<param>元素来传递。

<id name="id" type="long" column="uid" unsaved-value="0">
        <generator class="net.sf.hibernate.id.TableHiLoGenerator">
                <param name="table">uid_table</param>
                <param name="column">next_hi_value_column</param>
        </generator>
</id>

所有的生成器都实现net.sf.hibernate.id.IdentifierGenerator接口。这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然,Hibernate提供了很多内置的实现。下面是一些内置生成器的快捷名字:

increment(递增)

用于为longshort或者int类型生成唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。 在集群下不要使用。

identity

对DB2,MySQL, MS SQL Server, Sybase和HypersonicSQL的内置标识字段提供支持。返回的标识符是longshort 或者int类型的。

sequence (序列)

在DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence),而在Interbase中使用生成器(generator)。返回的标识符是longshort或者 int类型的。

hilo (高低位)

使用一个高/低位算法来高效的生成longshort或者 int类型的标识符。给定一个表和字段(默认分别是是hibernate_unique_key 和next_hi)作为高位值得来源。高/低位算法生成的标识符只在一个特定的数据库中是唯一的。在使用JTA获得的连接或者用户自行提供的连接中,不要使用这种生成器。

seqhilo(使用序列的高低位)

使用一个高/低位算法来高效的生成longshort或者 int类型的标识符,给定一个数据库序列(sequence)的名字。

uuid.hex

用一个128-bit的UUID算法生成字符串类型的标识符。在一个网络中唯一(使用了IP地址)。UUID被编码为一个32位16进制数字的字符串。

uuid.string

使用同样的UUID算法。UUID被编码为一个16个字符长的任意ASCII字符组成的字符串。不能使用在PostgreSQL数据库中

native(本地)

根据底层数据库的能力选择identitysequence 或者hilo中的一个。

assigned(程序设置)

让应用程序在save()之前为对象分配一个标示符。

foreign(外部引用)

使用另外一个相关联的对象的标识符。和<one-to-one>联合一起使用。

5.1.4.2. 高/低位算法(Hi/Lo Algorithm)

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,或者Hibernate使用JTA获取应用服务器的数据源连接的时候无法使用hilo 。Hibernate必须能够在一个新的事务中得到一个"hi"值。在EJB环境中实现hi/lo算法的标准方法是使用一个无状态的session bean。

5.1.4.3. UUID算法(UUID Algorithm )

UUID包含:IP地址,JVM的启动时间(精确到1/4秒),系统时间和一个计数器值(在JVM中唯一)。在Java代码中不可能获得MAC地址或者内存地址,所以这已经是我们在不使用JNI的前提下的能做的最好实现了。

不要试图在PostgreSQL中使用uuid.string

5.1.4.4. 标识字段和序列(Identity columns and Sequences)

对于内部支持标识字段的数据库(DB2,MySQL,Sybase,MS SQL),你可以使用identity关键字生成。对于内部支持序列的数据库(DB2,Oracle, PostgreSQL, Interbase, McKoi,SAP DB),你可以使用sequence风格的关键字生成。这两种方式对于插入一个新的对象都需要两次SQL查询。

<id name="id" type="long" column="uid">
        <generator class="sequence">
                <param name="sequence">uid_sequence</param>
        </generator>
</id>
<id name="id" type="long" column="uid" unsaved-value="0">
        <generator class="identity"/>
</id>

对于跨平台开发,native策略会从identitysequence 和hilo中进行选择,取决于底层数据库的支持能力。

5.1.4.5. 程序分配的标识符(Assigned Identifiers)

如果你需要应用程序分配一个标示符(而非Hibernate来生成它们),你可以使用assigned生成器。这种特殊的生成器会使用已经分配给对象的标识符属性的标识符值。用这种特性来分配商业行为的关键字要特别小心(基本上总是一种可怕的设计决定)。

因为其继承天性,使用这种生成器策略的实体不能通过Session的saveOrUpdate()方法保存。作为替代,你应该明确告知Hibernate是应该被save还是update,分别调用Session的save()update()方法。

5.1.5. composite-id 联合ID

<composite-id
        name="propertyName"
        class="ClassName"
        unsaved-value="any|none"
        access="field|property|ClassName">

        <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()持久化状态之前,必须填充他的联合属性。我们会在第 7.4 节 “组件作为联合标识符(Components as composite identifiers)”章中说明一种更加方便的方法,把联合标识实现为一个独立的类,下面描述的属性只对这种备用方法有效:

  • name (可选):一个组件类型,持有联合标识(参见下一节)。

  • class (可选 - 默认为通过反射(reflection)得到的属性类型) : 作为联合标识的组件类名(参见下一节)。

  • unsaved-value (可选 - 默认为 none): 假如被设置为非none的值,就表示新创建,尚未被持久化的实例将持有的值。

5.1.6. 识别器(discriminator)

在"一棵对象继承树对应一个表"的策略中,<discriminator>元素是必需的,它声明了表的识别器字段。识别器字段包含标志值,用于告知持久化层应该为某个特定的行创建哪一个子类的实例。只能使用如下受到限制的一些类型: stringcharacterintegerbyteshortbooleanyes_notrue_false.

<discriminator
        column="discriminator_column"  (1)
        type="discriminator_type"      (2)
        force="true|false"             (3)
/>
(1)

column (可选 - 默认为 class) 识别器字段的名字

(2)

type (可选 - 默认为 string) 一个Hibernate字段类型的名字

(3)

force(强制) (可选 - 默认为 false) "强制"Hibernate指定允许的识别器值,就算取得的所有实例都是根类的。

标识器字段的实际值是根据<class> 和<subclass>元素的discriminator-value得来的.

force属性仅仅是在表包含一些未指定应该映射到哪个持久化类的时候才是有用的。这种情况不是经常会遇到。

5.1.7. 版本(version)(可选)

<version>元素是可选的,表明表中包含附带版本信息的数据。这在你准备使用 长事务(long transactions)的时候特别有用。(见后)

<version
        column="version_column"                            (1)
        name="propertyName"                                (2)
        type="typename"                                    (3)
        access="field|property|ClassName"                  (4)
        unsaved-value="null|negative|undefined"            (5)
/>
(1)

column (可选 - 默认为属性名): 指定持有版本号的字段名。

(2)

name: 持久化类的属性名。

(3)

type (可选 - 默认是 integer): 版本号的类型。

(4)

access (可选 - 默认是 property): Hibernate用于访问属性值的策略。

(5)

unsaved-value (可选 - 默认是undefined): 用于标明某个实例时刚刚被实例化的(尚未保存)版本属性值,依靠这个值就可以把这种情况和已经在先前的session中保存或装载的实例区分开来。(undefined指明使用标识属性值进行这种判断。)

版本号必须是以下类型:longintegershorttimestamp或者calendar

5.1.8. 时间戳(timestamp )(可选)

可选的<timestamp>元素指明了表中包含时间戳数据。这用来作为版本的替代。时间戳本质上是一种对乐观锁定的一种不是特别安全的实现。当然,有时候应用程序可能在其他方面使用时间戳。

<timestamp
        column="timestamp_column"           (1)
        name="propertyName"                 (2)
        access="field|property|ClassName"   (3)
        unsaved-value="null|undefined"      (4)
/>
(1)

column (可选 - 默认为属性名): 持有时间戳的字段名。

(2)

name: 在持久化类中的JavaBeans风格的属性名,其Java类型是 Date 或者 Timestamp的。

(3)

access (可选 - 默认是 property): Hibernate用于访问属性值的策略。

(4)

unsaved-value (可选 - 默认是null): 用于标明某个实例时刚刚被实例化的(尚未保存)版本属性值,依靠这个值就可以把这种情况和已经在先前的session中保存或装载的实例区分开来。(undefined指明使用标识属性值进行这种判断。)

注意,<timestamp> 和<version type="timestamp">是等价的。

5.1.9. property

<property>元素为类声明了一个持久化的,JavaBean风格的属性。

<property
        name="propertyName"                 (1)
        column="column_name"                (2)
        type="typename"                     (3)
        update="true|false"                 (4)
        insert="true|false"                 (4)
        formula="arbitrary SQL expression"  (5)
        access="field|property|ClassName"   (6)
/>
(1)

name: 属性的名字,以小写字母开头。

(2)

column (可选 - 默认为属性名字): 对应的数据库字段名。

(3)

type (可选): 一个Hibernate类型的名字。

(4)

update, insert (可选 - 默认为 true) :表明在用于UPDATE 和/或 INSERT的SQL语句中是否包含这个字段。这二者如果都设置为false则表明这是一个“外源性(derived)”的属性,它的值来源于映射到同一个(或多个)字段的某些其他属性,或者通过一个trigger(触发器),或者其他程序。

(5)

formula (可选): 一个SQL表达式,定义了这个计算(computed) 属性的值。计算属性没有和它对应的数据库字段。

(6)

access (可选 - 默认值为 property): Hibernate用来访问属性值的策略。

typename可以是如下几种:

  1. Hibernate基础类型之一(比如:integer, string, character,date, timestamp, float, binary, serializable, object, blob)。

  2. 一个Java类的名字,这个类属于一种默认基础类型 (比如: int, float,char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。

  3. 一个PersistentEnum的子类的名字。(比如:. eg.Color)。

  4. 一个可以序列化的Java类的名字。

  5. 一个自定义类型的类的名字。(比如: com.illflow.type.MyCustomType)。

如果你没有指定类型,Hibernarte会使用反射来得到这个名字的属性,以此来猜测正确的Hibernate类型。Hibernate会对属性读取器(getter方法)的返回类进行解释,按照规则2,3,4的顺序。然而,这并不足够。 在某些情况下你仍然需要type属性。(比如,为了区别Hibernate.DATEHibernate.TIMESTAMP,或者为了指定一个自定义类型。)

access属性用来让你控制Hibernate如何在运行时访问属性。在默认情况下,Hibernate会使用属性的get/set方法对。如果你指明access="field",Hibernate会忽略get/set方法对,直接使用反射来访问成员变量。你也可以指定你自己的策略,这就需要你自己实现net.sf.hibernate.property.PropertyAccessor接口,再在access中设置你自定义策略类的名字。

5.1.10. 多对一(many-to-one)

通过many-to-one元素,可以定义一种常见的与另一个持久化类的关联。这种关系模型是多对一关联。(实际上是一个对象引用。)

<many-to-one
        name="propertyName"                                (1)
        column="column_name"                               (2)
        class="ClassName"                                  (3)
        cascade="all|none|save-update|delete"              (4)
        outer-join="true|false|auto"                       (5)
        update="true|false"                                (6)
        insert="true|false"                                (6)
        property-ref="propertyNameFromAssociatedClass"     (7)
        access="field|property|ClassName"                  (8)
/>
(1)

name: 属性名。

(2)

column (可选): 字段名。

(3)

class (可选 - 默认是通过反射得到属性类型): 关联的类的名字。

(4)

cascade(级联) (可选): 指明哪些操作会从父对象级联到关联的对象。

(5)

outer-join(外连接) (可选 - 默认为 自动): 当设置hibernate.use_outer_join的时候,对这个关联允许外连接抓取。

(6)

update, insert (可选 - defaults to true) 指定对应的字段是否在用于UPDATE 和/或 INSERT的SQL语句中包含。如果二者都是false,则这是一个纯粹的“外源性(derived)”关联,它的值是通过映射到同一个(或多个)字段的某些其他属性得到的,或者通过trigger(除法器),或者是其他程序。

(7)

property-ref: (可选) 指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。

(8)

access (可选 - 默认是 property): Hibernate用来访问属性的策略。

cascade 属性允许下列值: allsave-updatedeletenone。设置除了none以外的其它值会传播特定的操作到关联的(子)对象中。参见后面的“Lifecycle Objects(自动管理生命周期的对象)”。

outer-join参数允许下列三个不同值:

  • auto (默认) 使用外连接抓取关联(对象),如果被关联的对象没有代理(proxy)

  • true 一直使用外连接来抓取关联

  • false 永远不使用外连接来抓取关联

一个典型的简单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"/>

当然,我们决不鼓励这种用法。

5.1.11. 一对一

持久化对象之间一对一的关联关系是通过one-to-one元素定义的。

<one-to-one
        name="propertyName"                                (1)
        class="ClassName"                                  (2)
        cascade="all|none|save-update|delete"              (3)
        constrained="true|false"                           (4)
        outer-join="true|false|auto"                       (5)
        property-ref="propertyNameFromAssociatedClass"     (6)
        access="field|property|ClassName"                  (7)
        
/>
(1)

name: 属性的名字。

(2)

class (可选 - 默认是通过反射得到的属性类型):被关联的类的名字。

(3)

cascade(级联) (可选) 表明操作是否从父对象级联到被关联的对象。

(4)

constrained(约束) (可选) 表明该类对应的表对应的数据库表,和被关联的对象所对应的数据库表之间,通过一个外键引用对主键进行约束。这个选项影响save()delete()在级联执行时的先后顺序(也在schema export tool中被使用)。

(5)

outer-join(外连接) (可选 - 默认为 自动): 当设置hibernate.use_outer_join的时候,对这个关联允许外连接抓取。

(6)

property-ref: (可选) 指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。

(7)

access (可选 - 默认是 property): Hibernate用来访问属性的策略。

有两种不同的一对一关联:

  • 主键关联

  • 惟一外键关联

主键关联不需要额外的表字段;两行是通过这种一对一关系相关联的,那么这两行就共享同样的主关键字值。所以如果你希望两个对象通过主键一对一关联,你必须确认它们被赋予同样的标识值!

比如说,对下面的EmployeePerson进行主键一对一关联:

<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实例被赋予和该Personemployee属性所指向的Employee实例同样的关键字值。

另一种方式是一个外键和一个惟一关键字对应,上面的EmployeePerson的例子,如果使这种关联方式,应该表达成:

<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>

如果在Person的映射加入下面几句,这种关联就是双向的:

<one-to-one name"employee" class="Employee" property-ref="person"/>

5.1.12. 组件(component), 动态组件(dynamic-component)

<component>元素把子对象的一些元素与父类对应的表的一些字段映射起来。 然后组件可以声明它们自己的属性、组件或者集合。参见后面的“Components”一章。

<component 
        name="propertyName"                 (1)
        class="className"                   (2)
        insert="true|false"                 (3)
        upate="true|false"                  (4)
        access="field|property|ClassName">  (5)
        
        <property ...../>
        <many-to-one .... />
        ........
</component>
(1)

name: 属性名

(2)

class (可选 - 默认为通过反射得到的属性类型):组件(子)类的名字。

(3)

insert: 被映射的字段是否出现在SQL的INSERT语句中?

(4)

update: 被映射的字段是否出现在SQL的UPDATE语句中?

(5)

access (可选 - 默认是 property): Hibernate用来访问属性的策略。

<property>子标签为子类的一些属性和表字段建立映射。

<component>元素允许加入一个<parent>子元素,在组件类内部就可以有一个指向其容器的实体的反向引用。

<dynamic-component>元素允许把一个Map映射为组件,其属性名对应map的键值。

5.1.13. 子类(subclass)

最后,多态持久化需要为父类的每个子类都进行声明。对于我们建议的“每一棵类继承树对应一个表”的策略来说,就需要使用<subclass>声明。

<subclass
        name="ClassName"                              (1)
        discriminator-value="discriminator_value"     (2)
        proxy="ProxyInterface"                        (3)
        lazy="true|false"                             (4)
        dynamic-update="true|false"
        dynamic-insert="true|false">

        <property .... />
        .....
</subclass>
(1)

name: 子类的全限定名。

(2)

discriminator-value(辨别标志) (可选 - 默认为类名):一个用于区分每个独立的子类的值。

(3)

proxy(代理) (可选): 指定一个类或者接口,在延迟装载时作为代理使用。

(4)

lazy(延迟装载) (可选): 设置lazy="true"是把自己的名字作为proxy接口的一种等价快捷方式。

每个子类都应该声明它自己的持久化属性和子类。 <version> 和<id> 属性可以从根父类继承下来。在一棵继承树上的每个子类都必须声明一个唯一的discriminator-value。如果没有指定,就会使用Java类的全限定名。

5.1.14. 连接的子类(joined-subclass)

另外一种情况,如果子类是持久化到一个属于它自己的表(每一个子类对应一个表的映射策略),那么就需要使用<joined-subclass>元素。

<joined-subclass
        name="ClassName"                    (1)
        proxy="ProxyInterface"              (2)
        lazy="true|false"                   (3)
        dynamic-update="true|false"
        dynamic-insert="true|false">

        <key .... >

        <property .... />
        .....
</joined-subclass>
(1)

name: 子类的全限定名。

(2)

proxy (可选): 指定一个类或者接口,在延迟装载时作为代理使用。

(3)

lazy(延迟装载) (可选): 设置lazy="true"是把自己的名字作为proxy接口的一种等价快捷方式。

这种映射策略不需要指定辨别标志(discriminator)字段。但是,每一个都必须使用<key>元素指定一个表字段包含对象的标识符。本章开始的映射可以被用如下方式重写:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.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>

5.1.15. map, set, list, bag

集合类在后面讨论。

5.1.16. 引用(import)

假设你的应用程序有两个同样名字的持久化类,但是你不想在Hibernate查询中使用他们的全限定名。除了依赖auto-import="true"以外,类也可以被显式地“import(引用)”。你甚至可以引用没有明确被映射的类和接口。

<import class="java.lang.Object" rename="Universe"/>
<import
        class="ClassName"              (1)
        rename="ShortName"             (2)
/>
(1)

class: 任何Java类的全限定名。

(2)

rename (可选 - 默认为类的全限定名): 在查询语句中可以使用的名字。

5.2. Hibernate 的类型

5.2.1. 实体(Entities)和值(values)

为了理解很多与持久化服务相关的Java语言级对象的行为,我们需要把它们分为两类:

实体entity 独立于任何持有实体引用的对象。与通常的Java模型相比,不再被引用的对象会被当作垃圾收集掉。实体必须被显式的保存和删除(除非保存和删除是从父实体向子实体引发的级联)。这和ODMG模型中关于对象通过可触及保持持久性有一些不同——比较起来更加接近应用程序对象通常在一个大系统中的使用方法。实体支持循环引用和交叉引用,它们也可以加上版本信息。

实体的持久化状态包含有指向其他实体的连接和一些 类型的实例。值是原始类型、集合、组件或者特定的不可变对象。与实体不同,值(特别是集合和组件)通过可触及性来进行持久化和删除的。因为值对象(和原始类型数据)是随着包含它们的实体而被持久化和删除的,它们不能够被独立的加上版本信息。值没有独立的标识,所以它们不能被两个实体或者集合共享。

所有的Hibernate对象,除了集合,都支持null语义。

直到现在,我们都一直使用"持久化对象"来代表实体。我们仍然会这么做。然而严格的来说,并不是所有用户定义的,带有持久化状态的类都是实体。组件(component)就是一个用户定义的类,仅仅由值语义构成。

5.2.2. 基本值类型

基本类型可以大致的分为:

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类型DATETIME 和TIMESTAMP (或等价类型)的映射。

calendar, calendar_date

java.util.Calendar 到SQL 类型TIMESTAMP和 DATE(或等价类型)的映射。

big_decimal

java.math.BigDecimal 到 NUMERIC (或者 Oracle 的NUMBER类型)的映射。

locale, timezone, currency

java.util.Localejava.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二进制类型。你也可以为一个并非默认为基本类型或者实现PersistentEnum接口的可序列化Java类或者接口指定Hibernate类型serializable

clob, blob

JDBC 类 java.sql.Clob 和 java.sql.Blob的映射。某些程序可能不适合使用这个类型,因为blob和clob对象可能在一个事务之外是无法重用的。(而且, 驱动程序对这种类型的支持充满着补丁和前后矛盾。)

实体及其集合的唯一标识可以是任何基础类型,除了binary、 blob 和 clob之外。(联合标识也是允许的,后面会说到。)

net.sf.hibernate.Hibernate中,定义了基础类型对应的Type常量。比如,Hibernate.STRING代表string 类型。

5.2.3. 持久化枚举(Persistent enum)类型

枚举(enumerated)类型是一种常见的Java习惯用语,它是一个类,拥有一些(不多)的不可变实例。你可以为枚举类型实现net.sf.hibernate.PersistentEnum接口,定义toInt() 和fromInt()方法:

package eg;
import net.sf.hibernate.PersistentEnum;

public class Color implements PersistentEnum {
    private final int code;
    private Color(int code) {
        this.code = code;
    }
    public static final Color TABBY = new Color(0);
    public static final Color GINGER = new Color(1);
    public static final Color BLACK = new Color(2);

    public int toInt() { return code; }

    public static Color fromInt(int code) {
        switch (code) {
            case 0: return TABBY;
            case 1: return GINGER;
            case 2: return BLACK;
            default: throw new RuntimeException("Unknown color code");
        }
    }
}

Hibernate可以使用枚举类的名字作为类型名,这个例子中就是eg.Color

5.2.4. 自定义值类型

开发者创建属于他们自己的值类型也是很容易的。比如说,你可能希望持久化java.lang.BigInteger类型的属性,持久化成为VARCHAR字段。Hibernate没有内置这样一种类型。自定义类型能够映射一个属性(或集合元素)到不止一个数据库表字段。比如说,你可能有这样的Java属性:getName()/setName(),这是java.lang.String类型的,对应的持久化到三个字段:FIRST_NAMEINITIALSURNAME

要实现一个自定义类型,可以实现net.sf.hibernate.UserTypenet.sf.hibernate.CompositeUserType中的任一个,并且使用类型的Java全限定类名来声明属性。请查看net.sf.hibernate.test.DoubleStringType这个例子,看看它是怎么做的。

<property name="twoStrings" type="net.sf.hibernate.test.DoubleStringType">
    <column name="first_string"/>
    <column name="second_string"/>
</property>

注意使用<column>标签来把一个属性映射到多个字段的做法。

虽然Hibernate内置的丰富类型和对component的支持意味着你可能很少需要使用自定义类型,至少对于你程序中经常出现的自定义类(并非实体)来说,这是一种好方法。比如说,MonetoryAmount(价格总额)对比使用CompositeUserType来说更好,虽然它可以很容易的使用一个component实现。这样做的动机之一是抽象。通过自定义类型,以后假若你改变表示金额值的方法时,你的映射文件不需要更改,这就得到了保护。

5.2.5. 映射到"任意"(any)类型

这是属性映射的又一种类型。<any>映射元素定义了一种从多个表到类的多形联合。这种类型的映射总是需要多于一个字段。第一个字段持有被从属的实体的类型。其他的字段持有标识符。对于这种类型的联合来说,不可能指定一个外键约束,所以当然这不是(多形)联合映射的通常方式。你只应该在非常特殊的情况下使用它(比如,审计log,用户会话数据等等)。

<any name="anyEntity" id-type="long" meta-type="eg.custom.Class2TablenameType">
    <column name="table_name"/>
    <column name="id"/>
</any>

meta-type属性让应用程序指定一个自定义类型,把数据库字段值映射到一个持久化类,该类的标识属性是用id-type定义的。如果meta-type返回java.lang.Class的实例,不需要其他处理。另一方面,如果是类似string或者character这样的基本类型,你必须指定从值到类的映射。

<any name="anyEntity" 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"                      (1)
        id-type="idtypename"                     (2)
        meta-type="metatypename"                 (3)
        cascade="none|all|save-update"           (4)
        access="field|property|ClassName"        (5)
>
        <meta-value ... />
        <meta-value ... />
        .....
        <column .... />
        <column .... />
        .....
</any>
(1)

name: 属性名。

(2)

id-type: 标识符类型。

(3)

meta-type (可选 - 默认为class): 一个用于把java.lang.Class映射到一个数据库字段的类或者允许分辨映射的类型。

(4)

cascade(级联) (可选- 默认为 none): 级联风格。

(5)

access (可选 - 默认是 property): Hibernate用来访问属性的策略。

老式的object 类型是用来在Hibernate 1.2中起到类似作用的,他仍然被支持,但是已经基本废弃了。

5.3. SQL中引号包围的标识符

你可强制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>

5.4. 映射文件的模块化(Modular mapping files)

允许在独立的映射文档中定义subclassjoined-subclass,直接位于hibernate-mapping下。这就可以让你每次扩展你的类层次的时候,加入新的映射文件就行了。在子类的映射中你必须指定一个extents属性,指明先前已经映射过的超类。使用这个功能的时候,一定要注意映射文件的排序是非常重要的!

<hibernate-mapping>
        <subclass name="eg.subclass.DomesticCat" extends="eg.Cat" discriminator-value="D">
             <property name="name" type="string"/>
        </subclass>
</hibernate-mapping>


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值