這裏整理下實體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). 因此處理時間類型數據時,你還需要定義將其存儲在數據庫中所預期的精度. 在數據庫中,表示時間類型的數據有DATE, TIME, 和 TIMESTAMP三種精度(即單純的日期,時間,或者兩者兼備). 可使用@Temporal註解來調整精度.
@Lob註解表示屬性將被持久化爲Blob或者Clob類型, 具體取決於屬性的類型, java.sql.Clob, Character[], char[] 和java.lang.String這些類型的屬性都被持久化爲Clob類型, 而java.sql.Blob, Byte[], 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 有兩個組件屬性, 分別是homeAddress和bornIn. 我們可以看到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生成器.
用的時候再詳細整理