1. 程式人生 > >Hibernate註釋下的自定義架構實現

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指令碼。用相結合的命名策略,你會獲得雙贏。