Hibernate Annotations--實體Bean

這裏整理下實體bean的註解規範以及Hibernate特有的擴展

現在EJB3實體Bean是純粹的POJO.實際上這表達了和Hibernate持久化實體對象同樣的概念. 它們的映射都通過JDK5.0註解來定義(EJB3規範中的XML描述語法至今還沒有最終定下來). 註解分爲兩個部分,分別是邏輯映射註解和物理映射註解, 通過邏輯映射註解可以描述對象模型,類之間的關係等等, 而物理映射註解則描述了物理的schema,表,列,索引等等. 下面我們在代碼中將混合使用這兩種類型的註解.

EJB3註解的API定義在javax.persistence.*包裏面. 大部分和JDK5兼容的IDE(象Eclipse, IntelliJ IDEA 和Netbeans等等)都提供了註解接口和屬性的自動完成功能. (這些不需要IDE提供特別的EJB3支持模塊,因爲EJB3註解是標準的JDK5註解)


---------------------------------------------------------------------------

聲明實體bean


每一個持久化的pojo類就是一個bean,我們通過在類中使用@Entity註釋來進行聲明!

例如:

package com.bubble.entity;

import javax.persistence.Entity;
import javax.persistence.Id;

/**
 * @author bubble
 *
 */
@Entity
public class Product {
	
	private int id;
	
	private String name;
	
	private String qq;
	

	public void setId(int id) {
		this.id = id;
	}
	@Id
	public int getId() {
		return id;
	}


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

	public String getName() {
		return name;
	}

	public void setQq(String qq) {
		this.qq = qq;
	}

	public String getQq() {
		return qq;
	}

}

這裏Product即被註釋爲一個持久類!

在對一個類進行註解時,你可以選擇對它的的屬性或者方法進行註解,根據你的選擇,Hibernate的訪問類型分別爲 field或property.(成員變量和屬性)

EJ3規範要求在需要訪問的元素上進行註解聲明,例如,如果訪問類型爲 property就要在getter方法上進行註解聲明, 

如果訪問類型爲 field就要在字段上進行註解聲明.應該儘量避免混合使用這兩種訪問類型. Hibernate根據@Id 或 @EmbeddedId的位置

來判斷訪問類型.


hibernate註解在field上還是property上好呢?


《中文版的 Expert one-on-one J2EE Development without EJB》第283頁下面有這樣的一句話:

根據譯者的觀察,如果將SETTER聲明爲PRIVATE,會導致HIBERNATE無法使用CGLIB優化的反射機制,只能通過標準的JAVA反射機制爲持久對象-----請注意,是整個對象的所有字段,而非僅僅那個聲明爲PRIVATE的字段----賦值。如果持久對象有較多字段,這個過程將相當耗時。 

--------------------------------------------------------

《中文版 POJOs in Action》第197頁:

表6.1映射成員變量和屬性的長處與短處
 長處                                                                                                    短處
  Property      封裝,Accessor可轉換值,默認支持                        必須定義Accessor(但可以爲private)                                           
 
  Field            無須定義Accessor----特別是setter方法                   封裝少,非默認支持,映射更煩瑣                                                 

--------------------------------------------------------------------

如果hibernate實體有繼承關係 
那麼父類中的標註寫到方法上,子類寫到屬性上,那麼則會hibernate映射會有問題,寫到哪裏都可以,但是千萬要統一。


---------------------------------------------------------------------------------------

定義表(Table)

@Table屬於類級別的註解,通過@Table可以指明該類對應的數據庫中的表名,目錄(catalog)和schema的名字!

之前也提到過,如果沒有定義@Table,則系統使用默認,即與類名相同。

例:在類名和表名不相同的情況下,你需要使用@Table來定義類對應的表

@Entity
@Table(name="t_product")
public class Product implements Serializable {
...
            

@Table元素包括了一個schema和一個catalog屬性,如果需要可以指定相應的值. 結合使用@UniqueConstraint註解可以定義表的唯一約束(unique constraint) (對於綁定到單列的唯一約束,請參考@Column註解)

@Table(name="t_product",
		    uniqueConstraints = {@UniqueConstraint(columnNames={"id", "qq"})}
		)

以上代碼中,爲數據庫表t_product定義了字段id,qq爲唯一約束,columnName數組中定義爲數據庫中的字段名!

PS:Hibernate在NamingStrategy的實現中定義了邏輯列名. 默認的EJB3命名策略將物理字段名當作邏輯字段名來使用. 注意該字段名和它對應的屬性名可能不同(如果字段名是顯式指定的話). 除非你重寫了NamingStrategy,否則不用擔心這些區別.


樂觀鎖定版本控制

你可以在實體bean中使用@Version註解,通過這種方式可添加對樂觀鎖定的支持:

@Entity
public class Flight implements Serializable {
...
    @Version
    @Column(name="OPTLOCK")
    public Integer getVersion() { ... }
}           

上面這個例子中,version屬性將映射到 OPTLOCK列, entity manager使用該字段來檢測更新衝突(防止更新丟失,請參考last-commit-wins策略).

根據EJB3規範,version列可以是numeric類型(推薦方式)也可以是timestamp類型. Hibernate支持任何自定義類型,只要該類型實現了UserVersionType.


-------------------------------------------------------------------------

映射簡單屬性

   聲明基本的屬性映射


Every non static non transient property (field or method) of an entity bean is considered persistent, unless you annotate it as @Transient. Not having an annotation for your property is equivalent to the appropriate @Basic annotation. The @Basicannotation allows you to declare the fetching strategy for a property:

實體bean中所有的非static非transient的屬性都可以被持久化, 除非你將其註解爲@Transient.所有沒有定義註解的屬性等價於在其上面添加了@Basic註解. 通過 @Basic註解可以聲明屬性的獲取策略(fetch strategy):

public transient int counter; //transient property

private String firstname; //persistent property

@Transient
String getLengthInMeter() { ... } //transient property

String getName() {... } // persistent property

@Basic
int getLength() { ... } // persistent property

@Basic(fetch = FetchType.LAZY)
String getDetailedComment() { ... } // persistent property

@Temporal(TemporalType.TIME)
java.util.Date getDepartureTime() { ... } // persistent property           

@Enumerated(EnumType.STRING)
Starred getNote() { ... } //enum persisted as String in database

上面這個例子中,counter是一個transient的字段, lengthInMeter的getter方法被註解爲@Transient, entity manager將忽略這些字段和屬性. 而name,length,firstname 這幾個屬性則是被定義爲可持久化和可獲取的.對於簡單屬性來說,默認的獲取方式是即時獲取(early fetch). 當一個實體Bean的實例被創建時,Hibernate會將這些屬性的值從數據庫中提取出來,保存到Bean的屬性裏. 與即時獲取相對應的是延遲獲取(lazy fetch).如果一個屬性的獲取方式是延遲獲取 (比如上面例子中的detailedComment屬性), Hibernate在創建一個實體Bean的實例時,不會即時將這個屬性的值從數據庫中讀出. 只有在該實體Bean的這個屬性第一次被調用時,Hibernate纔會去獲取對應的值. 通常你不需要對簡單屬性設置延遲獲取(lazy simple property),千萬不要和延遲關聯獲取(lazy association fetch)混淆了 (譯註:這裏指不要把lazy simple property和lazy association fetch混淆了).

注意

爲了啓用屬性級的延遲獲取,你的類必須經過特殊處理(instrumented): 字節碼將被織入原始類中來實現延遲獲取功能, 詳情參考Hibernate參考文檔.如果不對類文件進行字節碼特殊處理, 那麼屬性級的延遲獲取將被忽略.

推薦的替代方案是使用EJB-QL或者Criteria查詢的投影(projection)功能.

Hibernate和EJB3都支持所有基本類型的屬性映射. 這些基本類型包括所有的Java基本類型,及其各自的wrapper類和serializable類. Hibernate Annotations還支持將內置的枚舉類型映射到一個順序列(保存了相應的序列值) 或一個字符串類型的列(保存相應的字符串).默認是保存枚舉的序列值, 但是你可以通過@Enumerated註解來進行調整(見上面例子中的note屬性).

在覈心的Java API中並沒有定義時間精度(temporal precision). 因此處理時間類型數據時,你還需要定義將其存儲在數據庫中所預期的精度. 在數據庫中,表示時間類型的數據有DATETIME, 和 TIMESTAMP三種精度(即單純的日期,時間,或者兩者兼備). 可使用@Temporal註解來調整精度.

@Lob註解表示屬性將被持久化爲Blob或者Clob類型, 具體取決於屬性的類型, java.sql.ClobCharacter[]char[] 和java.lang.String這些類型的屬性都被持久化爲Clob類型, 而java.sql.BlobByte[]byte[] 和 serializable類型則被持久化爲Blob類型.

@Lob
public String getFullText() {
    return fullText;
}

@Lob 
public byte[] getFullCode() {
    return fullCode;
}
 

如果某個屬性實現了java.io.Serializable同時也不是基本類型, 並且沒有在該屬性上使用@Lob註解, 那麼Hibernate將使用自帶的serializable類型.


聲明列屬性

使用 @Column 註解可將屬性映射到列. 使用該註解來覆蓋默認值(關於默認值請參考EJB3規範). 在屬性級使用該註解的方式如下:

  • 不進行註解

  • 和 @Basic一起使用

  • 和 @Version一起使用

  • 和 @Lob一起使用

  • 和 @Temporal一起使用

  • 和 @org.hibernate.annotations.CollectionOfElements一起使用 (只針對Hibernate )

@Entity
public class Flight implements Serializable {
...
@Column(updatable = false, name = "flight_name", nullable = false, length=50)
public String getName() { ... }
            

在上面這個例子中,name屬性映射到flight_name列. 該字段不允許爲空,長度爲50,並且是不可更新的(也就是屬性值是不變的).

上面這些註解可以被應用到正規屬性上例如@Id 或@Version屬性.

@Column(
    name="columnName";                                (1)
    boolean unique() default false;                   (2)
    boolean nullable() default true;                  (3)
    boolean insertable() default true;                (4)
    boolean updatable() default true;                 (5)
    String columnDefinition() default "";             (6)
    String table() default "";                        (7)
    int length() default 255;                         (8)
    int precision() default 0; // decimal precision   (9)
    int scale() default 0; // decimal scale
(1)

name 可選,列名(默認值是屬性名)

(2)

unique 可選,是否在該列上設置唯一約束(默認值false)

(3)

nullable 可選,是否設置該列的值可以爲空(默認值false)

(4)

insertable 可選,該列是否作爲生成的insert語句中的一個列(默認值true)

(5)

updatable 可選,該列是否作爲生成的update語句中的一個列(默認值true)

(6)

columnDefinition 可選: 爲這個特定列覆蓋SQL DDL片段 (這可能導致無法在不同數據庫間移植)

(7)

table 可選,定義對應的表(默認爲主表)

(8)

length 可選,列長度(默認值255)

(8)

precision 可選,列十進制精度(decimal precision)(默認值0)

(10)

scale 可選,如果列十進制數值範圍(decimal scale)可用,在此設置(默認值0)


---------------------------------------

嵌入式對象(又名組件)

在實體中可以定義一個嵌入式組件(embedded component), 甚至覆蓋該實體中原有的列映射. 組件類必須在類一級定義@Embeddable註解. 在特定的實體的關聯屬性上使用@Embedded和 @AttributeOverride註解可以覆蓋該屬性對應的嵌入式對象的列映射:

@Entity
public class Person implements Serializable {

    // Persistent component using defaults
    Address homeAddress;

    @Embedded
    @AttributeOverrides( {
            @AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
            @AttributeOverride(name="name", column = @Column(name="bornCountryName") )
    } )
    Country bornIn;
    ...
}
            
@Embeddable
public class Address implements Serializable {
    String city;
    Country nationality; //no overriding here
}
            
@Embeddable
public class Country implements Serializable {
    private String iso2;
    @Column(name="countryName") private String name;

    public String getIso2() { return iso2; }
    public void setIso2(String iso2) { this.iso2 = iso2; }

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

嵌入式對象繼承其所屬實體中定義的訪問類型 (注意:這可以通過使用Hibernate提供的@AccessType註解來覆蓋原有值)(請參考 Hibernate Annotation Extensions).

在上面的例子中,實體bean Person 有兩個組件屬性, 分別是homeAddressbornIn. 我們可以看到homeAddress 屬性並沒有註解. 但是Hibernate自動檢測其對應的Address類中的@Embeddable註解, 並將其看作一個持久化組件.對於Country中已映射的屬性, 則使用@Embedded@AttributeOverride 註解來覆蓋原來映射的列名. 正如你所看到的, Address對象中還內嵌了Country對象, 這裏和homeAddress一樣使用了Hibernate和EJB3自動檢測機制. 目前EJB3規範還不支持覆蓋多層嵌套(即嵌入式對象中還包括其他嵌入式對象)的列映射. 不過Hibernate通過在表達式中使用"."符號表達式提供了對此特徵的支持.

    @Embedded
    @AttributeOverrides( {
            @AttributeOverride(name="city", column = @Column(name="fld_city") ),
            @AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ),
            @AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") )
            //nationality columns in homeAddress are overridden
    } )
    Address homeAddress;

Hibernate註解支持很多EJB3規範中沒有明確定義的特性. 例如,可以在嵌入式對象上添加 @MappedSuperclass註解, 這樣可以將其父類的屬性持久(詳情請查閱@MappedSuperclass).

Hibernate現在支持在嵌入式對象中使用關聯註解(如@*ToOne@*ToMany). 而EJB3規範尚不支持這樣的用法。你可以使用@AssociationOverride註解來覆寫關聯列.

在同一個實體中使用兩個同類型的嵌入對象, 其默認列名是無效的:至少要對其中一個進行明確聲明. Hibernate在這方面走在了EJB3規範的前面, Hibernate提供了NamingStrategy, 在使用Hibernate時, 通過NamingStrategy你可以對默認的機制進行擴展.DefaultComponentSafeNamingStrategy 在默認的EJB3NamingStrategy上進行了小小的提升, 允許在同一實體中使用兩個同類型的嵌入對象而無須額外的聲明.


無註解之屬性的默認值

如果某屬性沒有註解,該屬性將遵守下面的規則:

  • 如果屬性爲單一類型,則映射爲@Basic
  • 否則,如果屬性對應的類型定義了@Embeddable註解,則映射爲@Embedded
  • 否則,如果屬性對應的類型實現了Serializable, 則屬性被映射爲@Basic並在一個列中保存該對象的serialized版本
  • 否則,如果該屬性的類型爲java.sql.Clob 或 java.sql.Blob,則作爲@Lob並映射到適當的LobType.

-------------------------------------

映射主鍵屬性

使用@Id註解可以將實體bean中的某個屬性定義爲標識符(identifier). 該屬性的值可以通過應用自身進行設置, 也可以通過Hiberante生成(推薦). 使用 @GeneratedValue註解可以定義該標識符的生成策略:

  • AUTO - 可以是identity column類型,或者sequence類型或者table類型,取決於不同的底層數據庫.
  • TABLE - 使用表保存id值
  • IDENTITY - identity column
  • SEQUENCE - sequence

和EJB3規範相比,Hibernate提供了更多的id生成器.詳情請查閱 Hibernate Annotation Extensions .

下面的例子展示了使用SEQ_STORE配置的sequence生成器

@Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_STORE")
public Integer getId() { ... }
         

下面這個例子使用的是identity生成器

@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() { ... }
         

AUTO生成器適用於可移植的應用(在多個DB間切換). 多個@Id可以共享同一個identifier生成器,只要把generator屬性設成相同的值就可以了. 通過@SequenceGenerator 和@TableGenerator,你可以配置不同的identifier生成器. 每一個identifier生成器都有自己的適用範圍,可以是應用級(application level)和類一級(class level). 類一級的生成器在外部是不可見的, 而且類一級的生成器可以覆蓋應用級的生成器. 應用級的生成器則定義在包一級(package level)(如package-info.java):

@javax.persistence.TableGenerator(
    name="EMP_GEN",
    table="GENERATOR_TABLE",
    pkColumnName = "key",
    valueColumnName = "hi"
    pkColumnValue="EMP",
    allocationSize=20
)
@javax.persistence.SequenceGenerator(
    name="SEQ_GEN",
    sequenceName="my_sequence"
)
package org.hibernate.test.metadata;
         

如果在org.hibernate.test.metadata包下面的 package-info.java文件用於初始化EJB配置, 那麼該文件中定義的 EMP_GEN 和SEQ_GEN都是應用級的生成器. EMP_GEN定義了一個使用hilo算法 (max_lo爲20)的id生成器(該生成器將id的信息存在數據庫的某個表中.). id的hi值保存在GENERATOR_TABLE中. 在該表中 pkColumnName"key"等價於 pkColumnValue "EMP", 而valueColumnName "hi"中存儲的是下一個要使用的最大值.

SEQ_GEN則定義了一個sequence 生成器, 其對應的sequence名爲 my_sequence. 注意目前Hibernate Annotations還不支持sequence 生成器中的 initialValue和 allocationSize參數.

下面這個例子展示了定義在類範圍(class scope)的sequence生成器:

@Entity
@javax.persistence.SequenceGenerator(
    name="SEQ_STORE",
    sequenceName="my_sequence"
)
public class Store implements Serializable {
    private Long id;

    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_STORE")
    public Long getId() { return id; }
}
         

在這個例子中,Store類使用名爲my_sequence的sequence,並且SEQ_STORE 生成器對於其他類是不可見的. 注意在org.hibernate.test.metadata.id包下的測試代碼有更多演示Hibernate Annotations用法的例子..

下面是定義組合主鍵的幾種語法:

  • 將組件類註解爲@Embeddable,並將組件的屬性註解爲@Id
  • 將組件的屬性註解爲@EmbeddedId
  • 將類註解爲@IdClass,並將該實體中所有屬於主鍵的屬性都註解爲@Id

對於EJB2的開發人員來說 @IdClass是很常見的, 但是對於Hibernate的用戶來說就是一個嶄新的用法. 組合主鍵類對應了一個實體類中的多個字段或屬性, 而且主鍵類中用於定義主鍵的字段或屬性和 實體類中對應的字段或屬性在類型上必須一致.下面我們看一個例子:

@Entity
@IdClass(FootballerPk.class)
public class Footballer {
    //part of the id key
    @Id public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    //part of the id key
    @Id public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getClub() {
        return club;
    }

    public void setClub(String club) {
        this.club = club;
    }

    //appropriate equals() and hashCode() implementation
}

@Embeddable
public class FootballerPk implements Serializable {
    //same name and type as in Footballer
    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    //same name and type as in Footballer
    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    //appropriate equals() and hashCode() implementation
}

如上, @IdClass指向對應的主鍵類.

Hibernate支持在組合標識符中定義關聯(就像使用普通的註解一樣),而EJB3規範並不支持此類用法.

@Entity
@AssociationOverride( name="id.channel", joinColumns = @JoinColumn(name="chan_id") )
public class TvMagazin {
    @EmbeddedId public TvMagazinPk id;
    @Temporal(TemporalType.TIME) Date time;
}

@Embeddable
public class TvMagazinPk implements Serializable {
    @ManyToOne
    public Channel channel;
    public String name;
    @ManyToOne
    public Presenter presenter;
}

映射繼承關係

EJB3支持三種類型的繼承映射:

  • 每個類一張表(Table per class)策略: 在Hibernate中對應<union-class>元素:
  • 每個類層次結構一張表(Single table per class hierarchy)策略:在Hibernate中對應<subclass>元素
  • 連接的子類(Joined subclasses)策略:在Hibernate中對應 <joined-subclass>元素

你可以用 @Inheritance註解來定義所選擇的策略. 這個註解需要在每個類層次結構(class hierarchy) 最頂端的實體類上使用.

注意

目前還不支持在接口上進行註解.


-----------------------------------------------------------------

每個類一張表

這種策略有很多缺點(例如:多態查詢和關聯),EJB3規範, Hibernate參考手冊, Hibernate in Action,以及其他許多地方都對此進行了描述和解釋. Hibernate使用SQL UNION查詢來實現這種策略. 通常使用場合是在一個繼承層次結構的頂端:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable {
            

這種策略支持雙向的一對多關聯. 這裏不支持IDENTITY生成器策略,因爲id必須在多個表間共享. 當然,一旦使用這種策略就意味着你不能使用 AUTO 生成器和IDENTITY生成器.



待續,http://docs.jboss.org/hibernate/annotations/3.4/reference/zh_cn/html_single/

用的時候再詳細整理




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章