Chapter 19. 工具箱指南

通过Hibernate项目中提供的几个命令行工具(他们也被当作项目的一部分不断得到维护),还有XDoclet,Middlegen和AndroMDA内置的对Hibernate的支持,可以在几个不同的环境(SQL,java代码,xml映射文件)中进行相互转换(roundtrip)。

Hibernate的主发行包中附带了最重要的工具(甚至在Hibernate内部也可以快速调用这个工具):

Hibernate项目直接提供的其他工具在一个单独的发行包中发布,Hibernate Extensions。这个发行包包含了下列任务的工具:

实际上Hibernate Extensions里面还有一个工具:ddl2hbm。但是它已经被废弃了,已经不再被维护了。Middlegen完成了同样的任务,并且更加出色。

对Hibernate提供支持的第三方工具有:

这些第三方工具没有在这篇指南中说明。请查阅Hibernate 网站得到关于它们目前的情况。(Hibernate主发行包中有关于整个网站的快照)

19.1. Schema 生成器(Schema Generation)

可以从你的映射文件使用一个命令行工具生成DDL。在Hibernate主发行包的hibernate-x.x.x/bin目录下有一个批处理文件。

生成的schema包含有对实体和集合类表的完整性引用约束(主键和外键)。涉及到的标示符生成器所需的表和sequence也会同时生成。

在使用这个工具的时候,你必须 通过hibernate.dialet属性指定一个SQL方言(Dialet)

19.1.1. 对schema定制化(Customizing the schema)

很多Hibernate映射元素定义了一个可选的length属性。你可以通过这个属性设置字段的长度。

有些tag接受not-null属性(用来在表字段上生成NOT NULL约束)和unique属性(用来在表字段上生成UNIQUE约束)。

有些tag接受index属性,用来指定字段的index名字。unique-key属性可以对成组的字段指定一个组合键约束(unit key constraint)。目前,unique-key属性指定的值并不会被当作这个约束的名字,它们只是在用来在映射文件内部用作区分的。

示例:

<property name="foo" type="string" length="64" not-null="true"/>

<many-to-one name="bar" foreign-key="fk_foo_bar" not-null="true"/>

<element column="serial_number" type="long" not-null="true" unique="true"/>

另外,这些元素还接受<column>子元素。在定义跨越多字段的类型时特别有用。

<property name="foo" type="string">
    <column name="foo" length="64" not-null="true" sql-type="text"/>
</property>

<property name="bar" type="my.customtypes.MultiColumnType"/>
    <column name="fee" not-null="true" index="bar_idx"/>
    <column name="fi" not-null="true" index="bar_idx"/>
    <column name="fo" not-null="true" index="bar_idx"/>
</property>

sql-type属性允许用户覆盖默认的Hibernate类型到SQL数据类型的映射。

Table 19.1. Summary

属性(Attribute)值(Values)解释(Interpretation)
lengthtrue|false字段长度
not-nulltrue|false指明字段是否应该是非空的
uniquetrue|false指明是否该字段具有惟一约束
indexindex_name指明一个(多字段)的索引(index)的名字
unique-keyunique_key_name指明多字段惟一约束的名字(参见上面的说明)
foreign-keyforeign_key_name 指明一个外键的名字,它是为关联生成的。
sql-typecolumn_type 覆盖默认的字段类型(只能用于<column>属性)

19.1.2. 运行该工具

SchemaExport工具把DDL脚本写到标准输出,同时/或者执行DDL语句。

java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2ddl.SchemaExport options mapping_files

Table 19.2. SchemaExport命令行选项

选项说明
--quiet不要把脚本输出到stdout
--drop只进行drop tables的步骤
--text不执行在数据库中运行的步骤
--output=my_schema.ddl把输出的ddl脚本输出到一个文件
--config=hibernate.cfg.xml从XML文件读入Hibernate配置
--properties=hibernate.properties从文件读入数据库属性
--format把脚本中的SQL语句对齐和美化
--delimiter=x为脚本设置行结束符

你甚至可以在你的应用程序中嵌入SchemaExport工具:

Configuration cfg = ....;
new SchemaExport(cfg).create(false, true);

19.1.3. 属性(Properties)

可以通过如下方式指定数据库属性:

  • 通过-D<property>系统参数

  • hibernate.properties文件中

  • 位于一个其它名字的properties文件中,然后用 --properties参数指定

所需的参数包括:

Table 19.3. SchemaExport 连接属性

属性名说明
hibernate.connection.driver_classjdbc driver class
hibernate.connection.urljdbc url
hibernate.connection.usernamedatabase user
hibernate.connection.passworduser password
hibernate.dialect方言(dialect)

19.1.4. 使用Ant(Using Ant)

你可以在你的Ant build脚本中调用SchemaExport:

<target name="schemaexport">
    <taskdef name="schemaexport"
        classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"
        classpathref="class.path"/>
    
    <schemaexport
        properties="hibernate.properties"
        quiet="no"
        text="no"
        drop="no"
        delimiter=";"
        output="schema-export.sql">
        <fileset dir="src">
            <include name="**/*.hbm.xml"/>
        </fileset>
    </schemaexport>
</target>

19.1.5. 对schema的增量更新(Incremental schema updates)

SchemaUpdate工具对已存在的schema采用"增量"方式进行更新。注意SchemaUpdate严重依赖于JDBC metadata API,所以它并非对所有JDBC驱动都有效。

java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2ddl.SchemaUpdate options mapping_files

Table 19.4. SchemaUpdate命令行选项

选项说明
--quiet不要把脚本输出到stdout
--properties=hibernate.properties从指定文件读入数据库属性

你可以在你的应用程序中嵌入SchemaUpdate工具:

Configuration cfg = ....;
new SchemaUpdate(cfg).execute(false);

19.1.6. 用Ant来增量更新schema(Using Ant for incremental schema updates)

你可以在Ant脚本中调用SchemaUpdate

<target name="schemaupdate">
    <taskdef name="schemaupdate"
        classname="net.sf.hibernate.tool.hbm2ddl.SchemaUpdateTask"
        classpathref="class.path"/>
    
    <schemaupdate
        properties="hibernate.properties"
        quiet="no">
        <fileset dir="src">
            <include name="**/*.hbm.xml"/>
        </fileset>
    </schemaupdate>
</target>

19.2. 代码生成(Code Generation)

Hibernate代码生成器可以用来为Hibernate映射文件生成Java实现类的骨架。这个工具在Hibernate Extensions发行包中提供(需要单独下载)。

hbm2java解析映射文件,生成可工作的Java源代码文件。使用hbm2java,你可以“只”提供.hbm文件,不用担心要去手工编写Java文件。

java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2java.CodeGenerator options mapping_files

Table 19.5. 代码生成器命令行选项

选项说明
--output=output_dirroot directory for generated code生成代码输出的根目录
--config=config_fileoptional file for configuring hbm2java可选的hvm2java配置文件

19.2.1. 配置文件(可选)

配置文件提供了配置生成源代码的多个"渲染器(renders)"的途径,也可以声明在全局范围生效的<meta>属性。详情请参见<meta>属性的部分。

<codegen>
    <meta attribute="implements">codegen.test.IAuditable</meta>
    <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
    <generate
        package="autofinders.only"
        suffix="Finder"
        renderer="net.sf.hibernate.tool.hbm2java.FinderRenderer"/>
</codegen>

这个配置文件声明了一个全局的meta(元)属性“implements”,指定了两个渲染器,默认渲染器(BadicRender)和生成Finder(参见下面的“基本Finder 生成器”)的渲染器。

定义第二个渲染器需要一个包名和后缀属性。

包名属性指定生成后的源代码应该保存的位置,覆盖在.hbm文件中指定的包范围。

后缀属性指定生成的文件的后缀。比如说,如果有一个Foo.java文件,应该变成FooFinder.java

19.2.2. meta属性

<meta>标签时对hbm.xml文件进行的简单注解,工具可以用这个位置来保存/阅读和Hibernate内核不是直接相关的一些信息。

你可以用<meta>标签来告诉hbm2java只生成"protectd"

下面的例子:

<class name="Person">
    <meta attribute="class-description">
        Javadoc for the Person class
        @author Frodo
    </meta>
    <meta attribute="implements">IAuditable</meta>
    <id name="id" type="long">
        <meta attribute="scope-set">protected</meta>
        <generator class="increment"/>
    </id>
    <property name="name" type="string">
        <meta attribute="field-description">The name of the person</meta>
    </property>
</class>

会生成类似下面的输出(为了有助于理解,节选部分代码)。注意Javadoc注释和声明成protected的set方法:

// default package

import java.io.Serializable;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

/** 
 *         Javadoc for the Person class
 *         @author Frodo
 *     
 */
public class Person implements Serializable, IAuditable {

    /** identifier field */
    public Long id;

    /** nullable persistent field */
    public String name;

    /** full constructor */
    public Person(java.lang.String name) {
        this.name = name;
    }

    /** default constructor */
    public Person() {
    }

    public java.lang.Long getId() {
        return this.id;
    }

    protected void setId(java.lang.Long id) {
        this.id = id;
    }

    /** 
     * The name of the person
     */
    public java.lang.String getName() {
        return this.name;
    }

    public void setName(java.lang.String name) {
        this.name = name;
    }

}

Table 19.6. 支持的meta标签

属性说明
class-description插入到类的javadoc说明去
field-description插入到field/property的javadoc说明去
interface如果是true,生成interface而非class
implements类要实现的接口
extends类要继承的超类(若是subclass,则忽略该属性)
generated-class重新指定要生成的类名
scope-classclass的scope
scope-setset方法的scope
scope-getget方法的scope
scope-field实际属性字段(field)的scope
use-in-tostringtoString()中包含此属性
bound为属性增加propertyChangeListener支持
constrained为属性增加vetoChangeListener支持
gen-property如果是false,不会生成属性(谨慎使用)
property-type覆盖属性的默认值.如果值是标签,则指定一个具体的类型而非Object(Use this with any tag's to specify the concrete type instead of just Object.)
finder-method参见下面的"Basic finder generator"
session-method参见下面的"Basic finder generator"

通过<meta>标签定义的属性在一个hbm.xml文件中是默认"继承"的。

这究竟是什么意思?如果你希望你所有的类都实现IAuditable接口,那么你只需要加一个<meta attribute="implements">IAuditable</meta> 在你hml.xml文件的开头,就在<hibernate-mapping>后面。现在所有在hbm.xml文件中定义的类都会实现IAuditable了!(除了那些也特别指定了"implements"元属性的类,因为本地指定的元标签总是会覆盖任何继承的元标签)。

注意,这条规则对所有<meta>标签都有效。也就是说它可以用来指定所有的字段都被声明成protected的,而非默认的private。这可以通过在<class>后面<meta attribute="scope-field">protected</meta>指定,那么这个类所有的field都会变成protected。

如果你不想让<meta>标签继承,你可以简单的在标签属性上指明inherit="false",比如<meta attribute="scope-class" inherit="false">public abstract</meta>,这样"class-scope"就只会对当前类起作用,不会对其子类生效。

19.2.3. 基本的finder生成器(Basic finder generator)

目前可以让hbm2java为Hibernate属性生成基本的finder。这需要在hbm.xml文件中做两件事情。

首先是要标记出你希望生成finder的字段。你可以通过在property标签中的meta 块来定义:

<property name="name" column="name" type="string">
     <meta attribute="finder-method">findByName</meta>
</property>

find方法的名字就是meta标签中间的文字。

第二件事是为hbm2java建立下面格式的配置文件:

<codegen>
    <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
    <generate suffix="Finder" renderer="net.sf.hibernate.tool.hbm2java.FinderRenderer"/>
</codegen>

然后用参数去调用:hbm2java --config=xxx.xmlxxx.xml就是你刚才创建的配置文件的名字。

有个可选的参数,作为一个在class级别的meta标签,格式如下:

<meta attribute="session-method">
    com.whatever.SessionTable.getSessionTable().getSession();
</meta>

他是用来管理你如何使用Thread Local Session模式(在Hibernate 网站的Design Patterns部分有文档)得到session的。

19.2.4. 基于Velocity的渲染器/生成器(Velocity based renderer/generator)

目前可以使用velocity作为渲染机制的一个替代方案。下面的config.xml文件显示了如果配置hbm2java来使用velocity渲染器。

    <codegen>
     <generate renderer="net.sf.hibernate.tool.hbm2java.VelocityRenderer">
      <param name="template">pojo.vm</param>
     </generate>
    </codegen>

名为template的参数是指向你希望你使用velocity macro文件的资源路径。这个文件必须在hbm2java的classpath中。所以要记住把pojo.vm所在的路径加入到你ant任务或者shell脚本中去。(默认的位置是./tools/src/velocity)

注意,当前的pojo.vm只生成java beans最基本的部分。他还没有默认的渲染器那么完整,也没有那么多功能——特别是大部分meta标签还不支持。

19.3. 映射文件生成器(Mapping File Generation)

映射文件的骨架可以从编译过的持久化类中使用MapGenerator工具生成。这工具是Hibernate Extensions发行包的一部分。

Hibernate映射生成器提供了从编译过的类中产生映射的机制。他使用Java反射来查找属性( properties),然后使用启发式算法来从属性类型猜测合适的映射。生成出来的映射文件之应该看作是后续工作的起点。没有办法在没有用户修正的情况下生成完整的Hibernate映射。但是,这个工具还是替你做了很多非常琐碎和麻烦的工作。

类一个一个地加入到映射去。如果工具认为某个类不是Hibernate可持久化( persistable)的,就会把这些类剔除。

判断是否是Hibernate可持久化( persistable)的原则是:

  • 必定不是一个原始类型

  • 必定不是一个数组

  • 必定不是一个接口

  • 必定不是一个内部类

  • 必定有一个默认的无参数的构造方法。

注意,接口和内部类实际上是可以通过Hibernate持久化的,但是一般来说用户不会使用。

对已经发现的类,MapGenerator会重复回溯到超类链条上去,以尽可能的把Hibernate可持久化的超类加入到对同一个数据库表的映射去。如果回溯过程中某个类出现了有个属性在下列备选UID名字(candidate UID names)名单中,回溯就会停止。

默认的备选UID属性名有:uid, UID, id, ID, key, KEY, pk, PK

如果类中有两个方法,一个是setter,一个是getter,并且setter的单参数的属性和getter的无参数返回值得类型相同,并且setter返回void,就认为发现了一个属性。并且,setter的名字必须以set字符串开始,getter的名字必须以get开始,或者以is开始并且属性类型是boolean。在上面的情况发生时,get和set之后的名字还必须匹配。这个匹配就是属性的名字,然后如果第二个字母是小写的话,会把其首字母变成小写。

用来决定每个属性的数据库类型的规则如下:

  1. 如果Java类型是Hibernate.basic(),则属性是该类型的一个普通字段。

  2. 对于hibernate.type.Type特定类型和PersistentEnum来说,也会使用一个普通字段。

  3. 如果属性类型是一个数组,那么会使用一个Hibernate数组,并且MapGenerator试图反映数组元素的类型。(attempts to reflect on the array element type.)

  4. 如果属性是java.util.List,java.util.Map或者java.util.Set,会使用对应的Hibernate类型,但是MapGenerator不能对这些类型进行进一步处理了。

  5. 如果属性的类型不是上面任何一种,MapGeneraotr把决定数据库类型的步骤留待所有的类都被处理之后再来做。在那时候,如果类在上面描述过的超类搜索过程中被发现了,这个属性会被认为是一个many-to-one的关联。如果类有人和属性,它则是一个组件(component)。否则它就是可序列化的(serializable),或者不是可持久化的。

19.3.1. 运行此工具

这个工具会把XML映射写入到标准输出或者/并且到一个文件中去。

在调用这个工具的时候,你必须把你编译过的类放到classpath中去。

java -cp hibernate_and_your_class_classpaths net.sf.hibernate.tool.class2hbm.MapGenerator options and classnames

有两种操作模式:命令行或者交互式。

交互式模式当你使用一个惟一的命令行参数--interact的时候启动。这个模式提供一个命令控制台。你可以用uid=XXX命令设置每个类的UID属性的名字,XXX就是UID属性名。其他可用的命令就是类名的全限定名,或者“done”命令用来输出XML,并且结束。

在命令行模式下,下面的参数选项和所需处理的类的全限定名可以相互间隔使用。大多数选项会使用多次,每个只影响其后出现的类。

Table 19.7. MapGenerator命令行选项

选项说明
--quiet不把O-R 映射输出到stdout
--setUID=uid设置备选UID名单
--addUID=uid在备选UID名单前面增加一个新的uid
--select=mode对后面的classes使用select选择的模式(mode)(比如, distinct 或者all)
--depth=<small-int>限制后面的类的组件数据递归层数
--output=my_mapping.xml把O-R 映射输出到一个文件
full.class.Name把这个类加入到映射中
--abstract=full.class.Name参见下面的说明

abstract开关指定本工具忽略特定的超类,所以它的继承数上的类不会被映射到一个大表中去。比如,我们来看下面的类继承树:

Animal-->Mammal-->Human

Animal-->Mammal-->Marsupial-->Kangaroo

如果使用--abstract开关,Animal的所有子类都会被放到一个巨大的表中去,包含所有类的所有属性,还有一个用于分辨子类的字段。如果Mammal被标记成abstractHumanMarsupial会被映射到不同的<class>声明,并且会有各自单独的表。Kangaroo仍然会被认为是Marsupial的子类,除非Marsupial也标记为anstract的。