Hibernate註釋下的自定義架構實現
對於Java開發人員,Hibernate 3 annotations提供了非常好的方式來展示域分層。你可以很輕鬆的通過Hibernate自動生成需要的資料庫架構,帶有完整的SQL指令碼。然而回到現實世界,你還需要考慮到,有時資料庫管理員所使用的模糊的命名慣例。本文中,“Java Power Tools”的作者John Ferguson Smart將告訴你如何通過Hibernate自動生成資料庫架構,而且還方便資料庫管理。
Hibernate 3 註釋有一種強大的持久管理資料庫的方式,運用這些註釋,你不需要為XML對映檔案而費心,你可以設定成預設,由系統完成,從而減少了大量需要維護的程式碼。Hibernate提供了強大的自動生成資料庫架構的工具,因此Hibernate可以自動完成生成和更新資料庫架構的操作,你也無需擔心那些不可思議的SQL指令碼。
第一步:更新資料庫架構
用Hibernate自動更新資料庫架構很容易,你所需要做的只是設定好Hibernate.hbm2ddl.auto,如示例1:
示例1:
<hibernate-configuration> <session-factory> <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property> <property name="hibernate.hbm2ddl.auto">create-drop</property> ... <!-- Persistent classes --> <mapping class="com.mycompany.myapp.domain.Client"/> <mapping class="com.mycompany.myapp.domain.Order"/> ... </session-factory> </hibernate-configuration>
設定它的屬性為“create-drop”,那麼每次啟動應用程式都會產生新的資料庫,這對整合測試很有用,但是有些情況下卻不需要。另一方面,如果你設定這個值是為了更新,如果不存在資料庫,Hibernate只會自動建立資料庫,並更新與當前域模型匹配的所有表。
現在,在預設情況下,Hibernate將建立一套與Java類很相似的表及欄位,從Java開發者的角度來看這樣剛好符合要求。考慮下面的例子:
示例2:A simple persistent class
@Entity public class Client implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; ... } 這個類中,Hibernate在預設情況下建立SQL模式,可以繼續看示例3。 示例3:
create table Client (
id bigint generated by default as identity (start with 1),
firstName varchar(255),
lastName varchar(255),
...
primary key (id)
);
舊的命名慣例
資料庫的慣例約定有相當充分的理由存在,但是在任何情況下DBA常常要保守一些。開發者該如何做呢?
一個簡單的解決辦法是使用名字特徵:@Entity、
@Column註釋,這種方法優於預設情況下,如示例4:
示例4:
@Entity(name="T_CLIENT") public class Client implements Serializable { ... @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="CLIENT_ID") private Long id; @Column(name="FIRST_NAME") private String firstName; @Column(name="LAST_NAME") private String lastName; ... } 這樣很有作用,但是會有些麻煩,你不得不有更多的表。實際上,你必須對每個表及欄位都做這樣的操作。有沒有更好的方法呢? 當然,你可以在Hibernate會話中定義命名策略集合取代預設值。這樣的話你得寫一個類,說明Hibernate如何定義表及欄位名。 從ImprovedNamingStrategy類開始是一個恰當位置,用下劃線轉換類名,如SomeDomainEntity,轉換成some_domain_entity。 在啟動Hibernate會話時需準備這個類。如果你用Spring,需簡化建立命名策略bean併為
命名策略集合提供所建立的命名策略。 示例5就是Spring配置的典型說明: 示例5:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:/hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> <property name="namingStrategy" ref="namingStrategy" /> </bean> <bean id="namingStrategy" class="org.hibernate.cfg.ImprovedNamingStrategy"/>
在這種命名策略下,Hibernate將生成如下面示例6的指令碼: 示例6: create table client ( id bigint generated by default as identity (start with 1), first_name varchar(255), last_name varchar(255), ... primary key (id) ); 這樣雖然很好,但是它不能解決你所的問題。還需要資料庫命名約定。例如,每個表都可能從“T_”開始(比如T_CLIENT就是Client類), 或者表中的每個欄位可能以表中特定的字首開始(比如寫成CLI_FIRST_NAME和CLI_LAST_NAME)。為了能自動生成這些約束, 你寫自己的命名策略實施。 自定義命名策略實現 自定義命名策略最簡單的方法是擴充套件ImprovedNamingStrategy類。這個類提供一些使用者預設的情況,因此你只需實現你真正需要的方法就可以。 你可以不去理睬通用樣式任務的表名和列名方法,比如,把名稱都加上大寫字母。Hibernate生成表或列名時就會呼叫這種方法, 甚至是在註釋中明確指定的列名也會呼叫這種方法。,從ImprovedNamingStrategy類繼承而來的addUnderscores()方法,可以派上用場 ,如示例7所示: 示例7:public class MyNamingStrategy extends ImprovedNamingStrategy implements NamingStrategy { @Override public String columnName(String columnName) { return addUnderscores(columnName).toUpperCase(); } @Override public String tableName(String tableName) { return addUnderscores(tableName).toUpperCase(); } } 接下來的示例8,展現的是如何在表名前加“T_”字首,轉換成名字前加大寫字母和下劃線。 示例8:
@Override public String classToTableName(String className) { return "T_" + tableName(className); } @Override public String propertyToColumnName(String propertyName) { return addUnderscores(propertyName).toUpperCase(); }
一個更復雜的命名策略 命名策略介面實際上非常簡單,但在很多方面受限制。首先,在任何特定的時間你無法知道呼叫方法的引數是屬於哪個表。 這樣會受限制,比如,如果需要在表中每個列名前加表字首,正如資料庫約定所要求的。可以在classToTableName()方法中 加變數成員儲存當前的表,來解決這個限制。對於給定的表,這個方法將在propertyToColunmName()方法後被呼叫。例如, 示例9為表建立了三個字母的字首,加在表名及表中所有列名前。 示例9:public class MyNamingStrategy extends ImprovedNamingStrategy implements NamingStrategy { private String currentTablePrefix; @Override public String classToTableName(String className) { currentTablePrefix = className.substring(0, 3).toUpperCase() + "_"$$ return "T" + currentTablePrefix + tableName(className); } @Override public String propertyToColumnName(String propertyName) { return currentTablePrefix + addUnderscores(propertyName).toUpperCase(); } @Override public String columnName(String columnName) { return addUnderscores(columnName).toUpperCase(); } @Override public String tableName(String tableName) { return addUnderscores(tableName).toUpperCase(); } }
使用這種命名策略,Hibernate將產生如示例10的程式碼: 示例10:create table TCLI_CLIENT ( CLI_ID bigint generated by default as identity (start with 1), CLI_FIRST_NAME varchar(255), CLI_LAST_NAME varchar(255), ... primary key (CLI_ID) ); 外部關鍵字 一般很難自動生成的
外部關鍵字
,構成了一個麻煩的問題。預設情況下,Hibernate可隨機生成如“
FKAB1273D65CCF7AB”這樣的名字, DBA不會喜歡這樣自動產生的命名。解決這個問題,需要使用@ForeignKey註釋,如示例11所示: 示例11:
@Entity public class Order { ... @JoinColumn(name = "CLIENT_ID") @ManyToOne(optional = false) @ForeignKey(name = "FK_CLIENT_ORDERS") private Client client; ... }
多對多關係 當然,複雜的關係下(比如多對多的關係),上面所說的變得有些更復雜。例如,示例12中的SocialNerworker類,有許多朋友。 這種情況下,你需要使用像@JoinTable,@ForeignKey這樣的註釋。 示例12:
@Entity public class SocialNetworker { @ManyToMany @JoinTable(name = "TFRD_FRIEND", joinColumns = {@JoinColumn(name = "NETWORKER_ID") }, inverseJoinColumns = {@JoinColumn(name = "FRIEND_ID") } ) @ForeignKey(name = "FK_SNT_FRIENDS", inverseName="FK_FRD_FRIENDS") } private Set<SocialNetworker> friends = new HashSet<SocialNetworker>(); ... }
展示SQL指令碼 一旦你需要對資料庫做改動或更新時,資料庫管理員出於職責會很謹慎,可能會看要執行的SQL指令碼,Hibernate可以通過SchemaExport工具, 展示SQL指令碼。你使用這個工具把要完成的模式生成普通的SQL指令碼。 當然,此類操作你不想做為構建過程的一部分去做,如果使用Maven,例如,你使用Hibernate3-maven-plugin自動生成資料庫架構。關鍵部分如示例13所示,當然你可以設定drop和export為false,這樣就相當於沒有更新資料庫資料。 示例13:<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>hibernate3-maven-plugin</artifactId> <version>2.1</version> <executions> <execution> <phase>process-classes</phase> <goals> <goal>hbm2ddl</goal> </goals> </execution> </executions> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> </component> <component> <name>hbmdoc</name> </component> </components> <componentProperties> <configurationfile>/target/classes/hibernate.cfg.xml</configurationfile> <outputfilename>schema.ddl</outputfilename> <namingstrategy>mycompany.myapp.IRDNamingStrategy</namingstrategy> <drop>false</drop> <create>true</create> <export>false</export> <format>true</format> </componentProperties> </configuration> </plugin>
這樣會生成SQL指令碼,可以拿給DBA們看了。 總結 DBA命名慣例保留了下來,如果你將與他們共事,將需要顧及這些慣例。幸運的是,這並不意味著你要放棄Hibernate自動產生的 資料庫架構,也不用自已寫SQL指令碼。用相結合的命名策略,你會獲得雙贏。