通過Hibernate項目中提供的幾個命令行工具(他們也被當作項目的一部分不斷得到維護),還有XDoclet,Middlegen和AndroMDA內置的對Hibernate的支持,可以在幾個不同的環境(SQL,java代碼,xml映射文件)中進行相互轉換(roundtrip)。
Hibernate的主發行包中附帶了最重要的工具(甚至在Hibernate內部也可以快速調用這個工具):
-
從映射文件到DDL schema的生成器(也就是SchemaExport和hbm2ddl)
Hibernate項目直接提供的其他工具在一個單獨的發行包中發布,Hibernate Extensions。這個發行包包含了下列任務的工具:
-
從映射文件到Java源代碼的生成器(也就是CodeGenerator,hbm2java)
-
從已編譯的Java類或者帶有XDoclet標記的Java源代碼生成映射文件(它們是MapGenerator,class2hbm)
實際上Hibernate Extensions里面還有一個工具:ddl2hbm。但是它已經被廢棄了,已經不再被維護了。Middlegen完成了同樣的任務,并且更加出色。
對Hibernate提供支持的第三方工具有:
-
Middlegen (從現有的數據庫schema中生成映射文件)
-
AndroMDA ( 使用MDA思想(Model-Driven Architecture ,模型驅動體系)的代碼生成器,它從UML圖和其XML/XMI等價形式中生成持久化類的代碼)
這些第三方工具沒有在這篇指南中說明。請查閱Hibernate 網站得到關于它們目前的情況。(Hibernate主發行包中有關于整個網站的快照)
可以從你的映射文件使用一個命令行工具生成DDL。在Hibernate主發行包的hibernate-x.x.x/bin目錄下有一個批處理文件。
生成的schema包含有對實體和集合類表的完整性引用約束(主鍵和外鍵)。涉及到的標示符生成器所需的表和sequence也會同時生成。
在使用這個工具的時候,你必須 通過hibernate.dialet屬性指定一個SQL方言(Dialet)。
很多Hibernate映射元素定義了一個可選的length屬性。你可以通過這個屬性設置字段的長度。 (如果是Or, for numeric/decimal data types, the precision.)
有些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數據類型的映射。
check屬性允許用戶指定一個約束檢查。
<property name="foo" type="integer"> <column name="foo" check="foo > 10"/> </property> <class name="Foo" table="foos" check="bar < 100.0"> ... <property name="bar" type="float"/> </class>
表 15.1. Summary
屬性(Attribute) | 值(Values) | 解釋(Interpretation) |
---|---|---|
length | 數字 | 字段長度/小數點精度 |
not-null | true|false | 指明字段是否應該是非空的 |
unique | true|false | 指明是否該字段具有惟一約束 |
index | index_name | 指明一個(多字段)的索引(index)的名字 |
unique-key | unique_key_name | 指明多字段惟一約束的名字(參見上面的說明) |
foreign-key | foreign_key_name | 指明一個外鍵的名字,它是為關聯生成的。 |
sql-type | column_type | 覆蓋默認的字段類型(只能用于<column>屬性) |
check | SQL 表達式 | 對字段或表加入SQL約束檢查 |
SchemaExport工具把DDL腳本寫到標準輸出,同時/或者執行DDL語句。
java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2ddl.SchemaExport options mapping_files
表 15.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);
可以通過如下方式指定數據庫屬性:
-
通過-D<property>系統參數
-
在hibernate.properties文件中
-
位于一個其它名字的properties文件中,然后用 --properties參數指定
所需的參數包括:
你可以在你的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>
SchemaUpdate工具對已存在的schema采用"增量"方式進行更新。注意SchemaUpdate嚴重依賴于JDBC metadata API,所以它并非對所有JDBC驅動都有效。
java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2ddl.SchemaUpdate options mapping_files
表 15.4. SchemaUpdate命令行選項
選項 | 說明 |
---|---|
--quiet | 不要把腳本輸出到stdout |
--properties=hibernate.properties | 從指定文件讀入數據庫屬性 |
你可以在你的應用程序中嵌入SchemaUpdate工具:
Configuration cfg = ....; new SchemaUpdate(cfg).execute(false);
你可以在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>
Hibernate代碼生成器可以用來為Hibernate映射文件生成Java實現類的骨架。這個工具在Hibernate Extensions發行包中提供(需要單獨下載)。
hbm2java解析映射文件,生成可工作的Java源代碼文件。使用hbm2java,你可以“只”提供.hbm文件,不用擔心要去手工編寫Java文件。
java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2java.CodeGenerator options mapping_files
配置文件提供了配置生成源代碼的多個"渲染器(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。
也可以通過在<generate>元素上增加<param>屬性來傳遞特別的參數到渲染器去。
hbm2java目前支持一個這樣的參數,名字是generate-concrete-empty-classes來通知BasicRender對你所有的類都只生成空的具體類來繼承它們。下列config.xml演示了這個功能
<codegen> <generate prefix="Base" renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/> <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"> <param name="generate-concrete-empty-classes">true</param> <param name="baseclass-prefix">Base</param> </generate> </codegen>
注意,這個config.xml定義了兩個渲染器。一個生成Base類,第二個只生成空的具體類。
<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; } }
表 15.6. 支持的meta標簽
屬性 | 說明 |
---|---|
class-description | 插入到類的javadoc說明去 |
field-description | 插入到field/property的javadoc說明去 |
interface | 如果是true,生成interface而非class |
implements | 類要實現的接口 |
extends | 類要繼承的超類(若是subclass,則忽略該屬性) |
generated-class | 重新指定要生成的類名 |
scope-class | class的scope |
scope-set | set方法的scope |
scope-get | get方法的scope |
scope-field | 實際屬性字段(field)的scope |
use-in-tostring | 在toString()中包含此屬性 |
implement-equals | 在這個類中包含equals()和hashCode()方法 |
use-in-equals | 在equals()和hashCode() 方法中包含此屬性 |
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.) |
class-code | 在類的最后會插入的額外代碼 |
extra-import | 在所有的import后面會插入的額外的import |
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了?。ǔ四切┮蔡貏e指定了"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"就只會對當前類起作用,不會對其子類生效。
目前可以讓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.xml,xxx.xml就是你剛才創建的配置文件的名字。
有個可選的參數,作為一個在class級別的meta標簽,格式如下:
<meta attribute="session-method"> com.whatever.SessionTable.getSessionTable().getSession(); </meta>
他是用來管理你如何使用Thread Local Session模式(在Hibernate 網站的Design Patterns部分有文檔)得到session的。
目前可以使用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標簽還不支持。
映射文件的骨架可以從編譯過的持久化類中使用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之后的名字還必須匹配。這個匹配就是屬性的名字,然后如果第二個字母是小寫的話,會把其首字母變成小寫。
用來決定每個屬性的數據庫類型的規則如下:
-
如果Java類型是Hibernate.basic(),則屬性是該類型的一個普通字段。
-
對于hibernate.type.Type特定類型和PersistentEnum來說,也會使用一個普通字段。
-
如果屬性類型是一個數組,那么會使用一個Hibernate數組,并且MapGenerator試圖反映數組元素的類型。(attempts to reflect on the array element type.)
-
如果屬性是java.util.List,java.util.Map或者java.util.Set,會使用對應的Hibernate類型,但是MapGenerator不能對這些類型進行進一步處理了。
-
如果屬性的類型不是上面任何一種,MapGeneraotr把決定數據庫類型的步驟留待所有的類都被處理之后再來做。在那時候,如果類在上面描述過的超類搜索過程中被發現了,這個屬性會被認為是一個many-to-one的關聯。如果類有人和屬性,它則是一個組件(component)。否則它就是可序列化的(serializable),或者不是可持久化的。
這個工具會把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,并且結束。
在命令行模式下,下面的參數選項和所需處理的類的全限定名可以相互間隔使用。大多數選項會使用多次,每個只影響其后出現的類。
表 15.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被標記成abstract,Human和Marsupial會被映射到不同的<class>聲明,并且會有各自單獨的表。Kangaroo仍然會被認為是Marsupial的子類,除非Marsupial也標記為anstract的。