1. 程式人生 > >Hibernate 實體對映 @註解與xml 配置方式比較

Hibernate 實體對映 @註解與xml 配置方式比較

實體類與資料庫之間存在某種對映關係,Hibernate依據這種對映關係完成資料的存取,因此對映關係的配置在Hibernate中是最關鍵的。Hibernate支援xml配置檔案與@註解配置兩種方式。xml配置檔案是最基礎的配置@註解是Java的官方JPAJava Persistence API提供的。本章分別使用@註解與xml講解Hibernate的對映配置。


1  實體類的對映

Java的角度講,實體類就是普通的Java封裝類(有人稱為POJO有人稱為VO)。僅從實體類中的程式碼資訊,Hibernate並不能得知該實體類對應哪個資料表,因此還需要以某種方式配置一下。常用的方式有*.hbm.xml

檔案配置與@註解配置兩種。

hbm.xml檔案就是普通的xml檔案,hbmHibernate Mapping的縮寫,這樣從檔名上就能判斷該檔案為Hibernate實體類配置檔案。在JPA出現之前,Hibernate都使用hbm.xml檔案配置。JPA出現後,推薦使用JPA@註解配置,因為對於所有的ORM框架,@註解都是通用的。

1.1  使用@註解配置實體類

實體類一般有ID、普通屬性、集合屬性等,分別對應資料庫的主鍵、普通列、外來鍵。@註解配置中,實體類用@Entity註解,用@Table指定對應的資料表,用@Id配置主鍵,用@Column配置普通屬性,用@OneToMany@ManyToOne

@OneToOne@ManyToMany配置實體間關係等。實體類之間的關係可以看之前的學習筆記。下面編寫一個UsersVo類,在該類中使用@註解配置實體類的對映,UsersVo類的程式碼如下:

package com.cn.vo;
import javax.persistence.*;
@Entity // 註解Entity表示該類納入Hibernate管理,能夠被持久化
@Table(name = "users") // 指定該實體類對應的資料庫表名
public class UsersVo {
//  Fields
@Id // 指定該列為主鍵。
 // 這個註解用來表示主鍵型別, auto為資料庫自增長型別
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
 
@Column(name = "name") //指定變數對應的資料庫表的列為"name"
private String name;
 
@Column(name = "age")//指定變數對應的資料庫表的列為"age"
private int age;
 
@Column(name = "tel")//指定變數對應的資料庫表的列為"tel"
private String tel;
 
@Column(name = "address")//指定變數對應的資料庫表的列為"address"
private String address;
 
// Constructors
 
public UsersVo() {
}
 
public UsersVo(int id) {
this.id = id;
}
 
public UsersVo(int id, String name, int age, String tel,
String address) {
this.id = id;
this.name = name;
this.age = age;
this.tel = tel;
this.address = address;
}
 
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
}

上述程式碼中,所有的@註解都是javax.persistence.*下的,而不是org.hibernate.*下的。javax.persistence.*下的註解為JPA規範規定的註解,用於標註實體類與資料庫的對映關係,而org.hibernate.*下的註解僅用於補充,當某個功能JPA暫時不支援而Hibernate支援時使用。

1.2  使用XML檔案配置實體類對映

多個實體類可以配置在一個XML檔案中。Hibernate推薦XML對映檔案和實體類同名,便於閱讀和維護,比如UsersVo.java檔案對應UsersVo. hbm.xml檔案。XML檔案一般以“.hbm.xml”結尾,便於辨認,也可以直接用“.xml”結尾。例如UsersVo類的對映檔案可取名為“UsersVo.hbm.xml”或者“UsersVo.xml”。使用XML檔案配置實體類對映的時候,實體類和普通的實體類一樣,和資料庫的對映關係放在XML檔案中,UsersVo. hbm.xml檔案程式碼如下:

 <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- 
    Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<!--  name=””用來指定實體類, table="”用來指定資料庫表格 catalog表示連線的資料庫 lazy表示延遲載入 -->
    <class name="com.cn.vo.UsersVo" table="users" catalog="bank" lazy="false">
        <id name="id" type="java.lang.Integer"><!--  配置實體類變數-->
            <column name="id" /><!--  主鍵的欄位名  -->
            <generator class="assigned" /><!-- 主鍵 型別,這裡設定成不主動增長 -->
        </id>
        <property name="name" type="java.lang.String"><!--  配置實體類變數-->
            <column name="name" length="20" /><!--  資料庫的欄位名和欄位長度  -->
        </property>
        <property name="age" type="java.lang.Integer"><!--  配置實體類變數  -->
            <column name="age" length="3" /><!--  資料庫的欄位名和欄位長度  -->
        </property>
        <property name="tel" type="java.lang.String"><!--  配置實體類變數  -->
            <column name="tel" length="20" /><!--  資料庫的欄位名和欄位長度  -->
        </property>
        <property name="address" type="java.lang.String"><!--  配置實體類變數  -->
            <column name="address" length="50" /><!--  資料庫的欄位名和欄位長度  -->
        </property>
    </class>
</hibernate-mapping>

程式碼中的DOCTYPE指定的dtd檔案位於hibernate.jar中。dtd檔案為XML格式驗證檔案,Hibernate使用該dtd來驗證該XML檔案格式是否正確。如果hibernate.jar或者classpath中不存在該dtd檔案,Hibernate會到指定的URL下載該檔案。

在上述的對映檔案中,<class>中的name表示實體類的路徑和名稱。table="”用來指定資料庫表格 catalog表示連線的資料庫 lazy表示延遲載入,<id>用來宣告表的主鍵,<id>中的屬性name="id"表示實體類的屬性id。資料庫對應的欄位名放在<column>中,<column>中的name表示表中的欄位名,<generator class="assigned" />表示資料庫表的主鍵型別為預設的型別,預設的型別是不自動增長型別,在<property>中對映表中的其他欄位,<property>中的name屬性值表示實體類的屬性,type表示實體類屬性的型別。表字段的對映放在<column>中,name的值是欄位名,length表示欄位長度。

1.3  hibernate.cfg.xml檔案中配置實體類對映

實體類還需要配置到hibernate.cfg.xml中,以便Hibernate初始化實體類與資料庫表的對映關係。如果只配置了對映關係,而沒有配置到hibernate.cfg.xml中,Hibernate是無法解析實體類的,因為Hibernate無法自行判斷哪些是實體類。

如果實體類是使用@註解配置的,需要用<mapping class=””/>配置,而如果是用XML檔案配置的,需要用<mapping resource=””/>配置XML配置檔案。hibernate.cfg.xml檔案中配置實體類對映的示例程式碼如下:

<!-- 使用XML對映檔案對映實體類的配置 -->

<mapping resource="com/cn/vo/UsersVo.hbm.xml"/>

<mapping resource="com/cn/vo/DeptVo.hbm.xml"/>

<!-- 使用@註解對映實體類的配置 -->

<mapping class="com.cn.vo.UsersVo "/>

<mapping class="com.cn.vo.DeptVo "/>

1.4  配置主鍵對映

實體類最好有主鍵列,並有對應的gettersetter方法,這是Hibernate推薦的。主鍵儘量使用可以為null值的型別,例如IntegerLongString等,而不要使用intlong等。因為如果主鍵為null,則表示該實體類還沒有儲存到資料庫,是一個臨時狀態(Transient),而intlong等原始型別則不具備該功能。

1.  使用@註解配置主鍵

Hibernate中用@Id宣告該列為主鍵列,同時用@Column宣告該列的列名。當列名與屬性名相同時,@Column配置可省略。@GeneratedValue用於指定主鍵的生成策略。Hibernate支援多種逐漸生成規則,例如自增長、由某個表決定、由Sequence決定等等。如果不配置@GeneratedValue,則必須手動設定ID值。

@Id

@Column(name = "id")

// 設定主鍵型別, auto表示主鍵是自增長型別

@GeneratedValue(strategy = GenerationType.AUTO) 

private Integer id;

2.  XML檔案中配置主鍵

如果使用XML配置,主鍵用<id />配置,name指定實體類的主鍵屬性,column指定資料表中的主鍵列名。使用巢狀的<generator />配置主鍵生成策略,native表示使用資料庫自己的策略,在MySQL就是自增長型別,如果不用自動增長型別,則可以用assigned,例如:

<id name="id" column="id">

<generator class="native" />

</id>

1.5  主鍵生成規則

10.1.4節中講到了主鍵的配置,在配置主鍵過程中,配置了主鍵是否是自動生成的。@Id配置主鍵的同時,也要用@GeneratedValue配置主鍵生成規則。主鍵生成規則也成為主鍵生成策略,負責維護新實體的主鍵值。用的最多的策略是自增長策略。Hibernate還支援其他的多種主鍵生成規則。這些生成規則有些是資料庫提供的,有些是Hibernate提供的。

1.  使用@註解配置主鍵生成規則

到目前為止,@註解只支援四種逐漸生成策略:GenerationType.AUTOGenerationType.TABLEGenerationType. SEQUENCEGenerationType. IDENTITY,意義分別為:

 GenerationType.AUTO:自動方式,根據底層資料庫自動選擇。如果為MySQL等支援自增長型別的資料庫,則為自增長型別(auto_increment)。

 GenerationType.TABLE:使用指定的表來決定主鍵的取值,一般結合@TableGenerator使用,示例程式碼如下:

@Id

@TableGenerator(name = "tb_cat_gen", allocationSize = 1)

@GeneratedValue(strategy = GenerationType.TABLE, generator = "tb_cat_gen")

private Integer id;

 GenerationType. SEQUENCE:使用Sequence來決定主鍵的取值,適合Oracle、DB2、PostgreSQL、SAP DB等支援Sequence的資料庫,一般結合@ SequenceGenerator使用。注意某些資料庫如Oracle等沒有自增長型別,只能使用Sequence,示例程式碼如下:

@Id

@SequenceGenerator(name = "seq_cat", allocationSize = 25)

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_cat")

@Column(name = "id")

private Integer id;

 GenerationType. IDENTITY:支援DB2、MySQL、MS SQL Server、Sybase與 HypersonicSQL資料庫的identit型別主鍵。

2.  使用XML檔案配置主鍵生成規則

XML配置中支援的主鍵生成規則,比使用@註解的配置的主鍵生成規則要多,XML配置中支援的主鍵生成規則有以下幾種:

 native:取決於資料庫,相當於GenerationType.AUTO。

 identity:使用identity型別,相當於GenerationType. IDENTITY。

 sequence:使用sequence,相當於GenerationType. SEQUENCE。需要指定sequence的名稱,示例程式碼如下:

<id name="id" type=" java.lang.Integer " column=" id">
    <generator class="sequence"> <!-- 使用sequence主鍵生成規則 -->
         <param name="sequence">id_sequence</param>
    </generator>
</id>

 increment:自增長型別,由Hibernate而不是資料庫維護,因此即使Oracle等不支援自增長型別的資料庫也可以使用。

 hilo:hi/low演算法,使用指定的表給主鍵賦值,相當於GenerationType.TABLE。需要指定表名、列名等,例項程式碼如下:

<id name="id" type=" java.lang.Integer " column=" id">
   <generator class="hilo">
         <param name="table">users</param>
         <param name="column">id</param>
         <param name="max_lo">200</param>
   </generator>
</id>

seqhilo:基於sequence的hilo演算法,例如:
<id name="id" type=" java.lang.Integer " column="id">
   <generator class="seqhilo">
        <param name="sequence">hi_value</param>
           <param name="max_lo">200</param>
    </generator>
</id>

 uuid:使用128位的UUID演算法計算一個唯一的值,會使用IP地址及相關的計算機硬體資訊。計算結果為32位的16進位制數,對應的主鍵型別必須為String。

 guid:使用MySQL或者MS SQL Server等資料庫提供的GUID值。

 assigned:預設值,不使用任何策略,在儲存進資料庫之前必須使用setter方法賦值。

 select:使用資料庫觸發器賦值。

 foreign:使用外來鍵賦值,在一對一實體關係時,可保證關係雙方的Id保持一致。

MySQL資料庫與Hibernate都提供自增長策略,但是原理是不太一樣的。如果採用MySQL的自增長,插入資料時Hibernate生成的SQL語句中將不包含id主鍵列資料。該主鍵的當前值、下一個值由資料庫自己維護。如果使用Hibernate的自增長,插入資料時Hibernate生成的SQL語句將包含id主鍵列,並由Hibernate維護該主鍵的當前值以及下一個值。

對於普通的應用來說,資料庫自增長與Hibernate自增長在使用上沒有區別。但是如果某資料庫同時被兩個Hibernate程式使用,那麼此時使用Hibernate自增長將會出現錯誤。例如如果當前主鍵值為101,那麼Hibernate會認為下個主鍵值為102,兩個Hibernate程式插入資料時都會將主鍵值設為101,這時會因為主鍵衝突而導致其中一個寫資料失敗。

1.6  使用@註解配置普通屬性對映

這裡說的普通屬性,是指除了主鍵外的、Java基本型別的屬性,Integer型別與int型別是不同的,Integer預設為null,在資料庫中也表現為null,而int預設為0,在資料庫中也表現為0

普通屬性使用@Column@Basic配置。二者都可以省略。如果省略,則全部按照預設的規則配置,@Column@Basic的用法如下:

 @Column中可指定nullable(是否允許為null)、unique(是否唯一)、insertable(是否允許插入)、updatable(是否允許更新)、length(列長度)、columnDefinition(列型別)、scale(整數長度)、precision(小數點精度)等。這些屬性用於生成DDL建表語句。如果屬性對應的列名與屬性名一致,@Column可以省略。

 @Basic可為普通屬性配置載入方式,預設為即時載入。如果列資料比較大,例如大文字型別或者LOB型別,可配置為延遲載入。optional配置該列是否可為null。如果為true,表示該屬性是可選的,可以為null,否則不可以為null

@Column@Basic使用的程式碼示例如下:

@Column(name = "usersName", nullable = true, columnDefinition = "varchar", insertable = true, length = 255, unique = true, updatable = true, precision = 2, scale = 4) 

@Basic(fetch = FetchType.LAZY, optional=true)

private String usersName;

日期屬性也是普通的屬性,需要用@Basic宣告載入方式、@Column等指定列名,二者都可省略。另外,如果日期屬性是java.util.Date型別的,必須要用@Temporal配置日期型別,取值可以為DateTime或者Timestemp。否則Hibernate將無法區分該型別是到底是java.sql.Date(只有年月日等日期資訊)型別還是java.sql.Time(只有時分秒等時間資訊)型別、還是java.sql.TimeStamp(既有日期資訊、又有時間資訊)型別。例如:

@Temporal(TemporalType.TIMESTAMP) // 日期型別為DATE, TIME或者TIMESTEMP

@Column(name = " birthday")

private java.util.Date birthday;

在配置日期屬性時,如果屬性型別是java.util.Date型別,需要用@Temporal宣告日期型別。但是如果是java.sql.Timejava.sql.Date或者java.sql.TimeStamp型別的,型別本身就已經很明確了,不再需要@Temporal聲明瞭。

1.7  使用XML檔案配置普通屬性對映

XML中使用<property />標籤配置普通屬性。type屬性指定列型別,相當於@Column中的columnDefinition。例如,如果設定type=text”可以為String型別屬性設定為大文字型別列。不同於@註解中的@Column,如果屬性名與列名一致,column屬性可省略,xml配置中的<property>必須配置,否則視為不參與持久化的列。配置為: 

<property name="salary" precision="2" scale="10" length="255"
column="salary" type="string" update="true" insert="true"
lazy="false" unique="false" not-null="false">
</property>


在使用@註解配置中,如果沒有對普通屬性進行配置,則預設該屬性名與資料表列名相同;而xml檔案配置中,如果對普通屬性沒有配置,則認為該屬性沒有對應的資料庫列,不參與持久化。二者是截然不同的。

在配置日期型別屬性時,type屬性中指定日期型別,取值可以為datetimetimestamp等簡寫方式,也可以為java.sql.Datejava.sql.Timejava.sql.Timestamp等全寫方式。

同樣的道理,如果Java中屬性型別為java.util.Date型別,必須指定是java.sql.Date(只有年月日等日期資訊)型別還是java.sql.Time(只有時分秒等時間資訊)型別、還是java.sql.TimeStamp(既有日期資訊、又有時間資訊)型別。示例程式碼如下:

<property name=" birthday" type="date"></property>

1.8  配置臨時屬性對映

實體類可能有一些臨時屬性,在JPA中被稱為Transient屬性。這些屬性用於方便計算等其他用途,而不是儲存資料到資料庫中。這些屬性必須被標記為Transient,以便Hibernate把它們區別對待。否則Hibernate會試圖往資料庫寫該屬性,可能會因對應的列不存在而丟擲異常。

Java標註中,臨時屬性必須使用@Transient標註,既可以配置在臨時屬性上,也可以配置在對應的gettersetter方法上。例如:

@Transient

public int getCount() { // 臨時屬性,用於計算總記錄數

return name == null ? 0 :count;

}

如果只有形如gettersetter的方法,但是沒有對應的屬性,Hibernate仍然會認為該屬性存在。因此也需要用@Transient標註。 

而在XML配置中,所有沒有配置到XML檔案中的屬性都被視為臨時屬性。如果某屬性漏配置了,該屬性值將不被儲存到資料庫中。