Hibernate框架學習之注解映射實體類

?????前面的相關文章中,我們已經介紹了使用XML配置文件映射實體類及其各種類型的屬性的相關知識。然而不論是時代的潮流還是臃腫繁雜的配置代碼告訴我們,注解配置才是更人性化的設計,于是學習了基本的映射實體類的基本注解,此處做一點總結,后續文章將陸續更新使用注解的方式管理配置各種映射關聯關系。本篇主要涉及以下內容:

  • 使用最基本的注解映射一個實體類
  • 使用注解映射屬性
  • 使用注解映射主鍵
  • 其他特殊類型的屬性映射

一、使用最基本的注解映射一個實體類

@Entity
@Table(name = "userInfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    //省略getter和setter方法
}
//在hibernate.cfg.xml中添加實體類
//這樣hibernate就會根據配置文件去查找該實體類并做映射操作
<mapping class="User_Annotation.UserInfo"/>

這就是映射一個最簡單的實體類所用到的最基本的注解。其中,

  • @Entity:指定當前被修飾的類是一個實體類,用于映射到數據庫中的表。
  • @Table(name = "userInfo"):詳細指定了該類映射到數據庫中的哪張表,這里映射到userInfo表。
  • @Id:指定被修飾的屬性將映射到數據表的主鍵列。
  • @GeneratedValue(strategy = GenerationType.IDENTITY):該注解指定了主鍵的生成策略,一般不單獨出現,這里指定了主鍵自增的策略。
這里寫圖片描述

二、使用注解映射普通屬性
對于實體類中屬性的映射,一般我們使用@Column進行修飾。該注解有很多屬性:

  • name:指定該屬性映射到數據表中對應的名稱
  • nullable:指定該屬性映射的數據表中列是否可以為null,默認為true
  • unique:指定該屬性映射到數據表中的列是否具有唯一約束
  • length:指定該屬性映射到數據表中的列所能保存數據的最大長度,默認是255

默認情況下,我們不使用@Column修飾屬性的時候,hibernate會自動以該屬性的名稱映射到數據表中的列。

我們也可以使用注解@Transient修飾屬性,它指明了該屬性不會被映射到數據表中某一列,而只是作為一個屬性被定義在實體類中。例如:

@Entity
@Table(name = "userInfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @Transient
    private int age;
    //省略getter,setter方法
}

看看hibernate為我們生成的sql語句:

這里寫圖片描述

顯然,我們age屬性并沒有被映射到userinfo表中。

對于枚舉類型的屬性,我們可以使用@Enumerated注解進行修飾。
在某些特殊情況下,有時我們的實體類屬性會被定義為枚舉類型,那么對于這種數據庫中并無法對應的Java類型,該如何映射呢?Hibernate中提供@Enumerated注解來用于我們映射枚舉類型,該注解提供一個value屬性,該屬性可以取兩個值:

  • EnumType.STRING:該枚舉類型的屬性映射到數據表的字段的類型是字符串型
  • EnumType.ORDINAL:該枚舉類型的屬性映射到數據表的字段的類型是整數類型

例如:

//定義一個枚舉類型
public enum Season {
    春季, 夏季, 秋季, 冬季
}
@Entity
@Table(name = "userInfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    @Enumerated(EnumType.STRING)
    private Season season;
    //省略getter,setter方法
}

看看我們的userinfo表:

這里寫圖片描述

而當我們@Enumerated(EnumType.ORDINAL)修飾屬性的時候,那么Hibernate為我們生成的sql語句是:

這里寫圖片描述

這兩種情況下,數據表中的season字段一種保存的是枚舉類型的具體值,一種保存的是枚舉值對應的序號。

使用@Temporal注解映射日期時間類型
對于Java來說,表示時間的兩個類庫,Java.util.Date和java.util.Calendar。而對于數據庫而言,表示時間的類型就有很多,例如:date,time,datetime,timestamp等。如何準確的指定最終的映射情況就是我們的@Temporal注解的作用。@Temporal有一個value屬性,可以取以下的一些值:

  • TemporalType.DATE:對應于數據庫中的date類型
  • TemporalType.TIME:對應于數據庫中的time類型
  • TemporalType.TIMESTAMP:對應于數據庫中的timestamp類型

例如:

@Temporal(TemporalType.DATE)
private Date date;

上述代碼指定了Java.util.Date類型屬性映射到數據庫中的date類型字段。

三、使用注解映射主鍵屬性
最簡單的情況下,我們使用注解@Id標識實體類中的某個屬性,那么該屬性將會被hibernate映射到數據庫主鍵字段,并且無需指定任何屬性值。使用使用@GeneratedValue指定主鍵的生成策略,通過它的strategy屬性來指定具體的主鍵生成方案,該屬性可以取如下幾個值:

  • GenerationType.AUTO:hibernate默認為該值,它指明了hibernate自動根據底層數據庫選擇適當的生成策略
  • GenerationType.IDENTITY:適用于MySQL,SQLserver的主鍵自增長策略
  • GenerationType.SEQUENCE:適用于Oracle的子串策略
  • GenerationType.TABLE:基于輔助表的生成主鍵策略

如果不是使用Oracle做數據庫的話,一般我們會使用IDENTITY作為默認的主鍵生成策略。

聯合主鍵的映射可以通過多個@Id進行修飾即可,但要求該實體類必須繼承 java.io.Serializable并盡可能的重寫Object的兩個方法,hashCode和equals,因為多個屬性唯一確定一條記錄,自然需要比較屬性的值。例如:

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    private int id;
    @Id
    private String name;
    //省略getter,setter方法
}

看看hibernate為我們創建的表結構:

這里寫圖片描述

四、特殊屬性的映射
這里的特殊屬性指的是實體類中屬性類型非常規的基本類型、包裝類型、引用類型,而是類似于集合類型、自定義類型等。我們首先看對于集合類型的屬性映射情況。

1、映射集合類型的屬性
在hibernate中,所有的集合類型屬性都會被單獨映射到一張表中,無論是List,Set或者Map都會對應于一張新表。首先我們看List的映射,在詳細介紹之前,我們先完整的看看list的映射情況。

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    @ElementCollection(targetClass = String.class)
    @CollectionTable(name = "address",joinColumns = @JoinColumn(name = "user_id"))
    @OrderColumn(name = "list_id")
    @Column(name = "address")
    private List address;
    //省略getter,setter方法
}
//通過實體類實例向數據表中插入數據
UserInfo userInfo = new UserInfo();
userInfo.setName("single");
List<String> list = new ArrayList<String>();
list.add("NanJin");
list.add("XinJiang");
list.add("SiChuan");
list.add("ZheJiang");
list.add("NanTong");
userInfo.setAddress(list);

session.save(userInfo);

看看兩張表:

這里寫圖片描述

現在,我們再來看看所用到的幾個注解。@ElementCollection注解用于修飾一個集合類型的屬性,targetClass 指定了該集合類型的對應的泛型類型,我們這里指定了String類型,那么hibernate底層會默認構建一個ArrayList來存放所有的集合元素并且每個元素都限定為String類型。

@CollectionTable注解用于配置為集合屬性生成的那張新表的基本信息,name 指定新表的表名,joinColumns的值是一個注解@JoinColumn,該注解專門用于配置外鍵列,這里我們給他命名為user_id,該字段是address表的值依賴于userinfo表的id主鍵列的值。

@OrderColumn注解用于配置有序集合的序號,由于list是有序的集合,通過該注解將會在address表中增加一個字段保存各個元素在集合中的序號。

@Column注解則指向我們集合元素所在的列,可以配置他們列名等。

總的來說,一旦hibernate發現實體類中有集合類型的屬性需要映射,那么就會為集合屬性單獨映射出一張表,該表至少有兩個字段,一個字段依賴于主表的id字段值,在新表中相同該字段值的記錄共同組合成為實體類中的集合屬性的值,一個字段保存具體的集合元素的值信息。而對于有序集合來說,還應該包含一個字段用于保存每個集合元素在集合中的序號,該序號字段和第一個外鍵依賴字段組合成新表的聯合主鍵,唯一標識一條記錄。

在hibernate的管理下,當有數據添加進userinfo表的時候,hibernate將拿到該實體類實例的集合屬性的值,并連帶該實例的id一起插入到新表中。當然,當我們想要獲取一個userinfo實例的時候,hibernate也會為我們查詢address表,并注入到userinfo實例的集合屬性中,默認的注入模式是懶加載。

接著,我們看Set集合的映射情況。Set是一種無序并不重復的集合。具體的配置如下:

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @ElementCollection(targetClass = String.class)
    @CollectionTable(name = "address",joinColumns = @JoinColumn(name = "user_id",nullable = false))
    @Column(name = "value",nullable = false)
    private Set address;
    //省略getter,setter方法
}

相比List,Set由于是無序的,那么自然是沒有索引序列,所以無需配置@OrderColumn,但是它要求所有元素必須不可重復,那么通過制定nullable為false即可。
看看表的生成情況:

這里寫圖片描述

對于像set一樣的無序集合,新表的主鍵有user_id和value列聯合作為主鍵,可以保證唯一確定一條數據記錄。

最后,我們看看一下Map的映射情況,先看代碼:

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @ElementCollection(targetClass = String.class)
    @MapKeyClass(Integer.class)
    @CollectionTable(name = "address",joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "value")
    private Map map;
    //省略getter,setter方法
}

具體的表生成情況:

這里寫圖片描述

對于map這種鍵值對集合,targetClass 用于指定value值的類型,而@MapKeyClass則用于指定key值的類型,其他的幾乎沒什么變化,對于map集合映射出來的表,user_id和map的key字段將聯合組成此表的主鍵,唯一確定一條記錄。

對于性能的要求,hibernate不推薦實體類屬性使用數組類型,建議優先使用集合類型。

2、組件屬性映射
所謂的組件類型就是指我們自定義的類類型,在某些情況下,實體類中包含自定類型也是很常見的,那么對于我們自定義的類型該如何來映射到數據表呢?Hibernate的映射策略很簡單,對于組件中的每個屬性都映射出一個列,也就是相當于把組件給拆解了。例如:

//首先定義一個組件類
@Embeddable
public class Disposition {
    private String mood;
    private String hobby;
    //省略getter,setter方法
}

我們定義了一個類,Disposition并使用@Embeddable注解修改該類。當Hibernate對整個類路徑進行掃描的時候,就會注冊該類為一個組件類型,那么當我們在實體類中引用該類型的時候,hibernate就能找到相應的組件類型。

@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    
    private Disposition disposition;
    //省略getter,setter方法
}

最后生成的數據表結構如下:

這里寫圖片描述

組件類的每個屬性都被映射到userinfo表中了。當我們通過實體類實例向數據表中插入數據的時候,hibernate會將組件類實例拆分出來的各個屬性插入到對應的表字段。當我們通過數據表獲取userinfo實例的時候,hibernate判斷userinfo中有一個組件類屬性,于是創建組件類實例并裝載相應的數據表中的數值賦值給userinfo的組件類型屬性。

3、集合屬性為組件類型的表級映射
集合中的元素除了可以是基本類型,包裝類型以外,還可以是組件類型,也就是復合類型。那么對于他們的映射卻稍顯不同,例如:

//定義一個復合類型
@Embeddable
public class Person {
    private String name;
    private int age;
    private String address;
    //省略getter,setter方法
}
@Entity
@Table(name = "userInfo")
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    @ElementCollection(targetClass = Person.class)
    @CollectionTable(name = "persons",joinColumns = @JoinColumn(name = "user_id"))
    @OrderColumn(name = "list_index")
    private List list;
    //省略getter,setter方法
}

顯然,在實體類中的集合類型屬性的映射,大體上是一樣的。首先我們通過targetClass 屬性指定集合中的元素類型,通過CollectionTable配置為集合生成的新表的基本信息,通過OrderColumn指定索引列。當然,這里我們不需要使用Column注解配置集合元素本身在數據表中的字段名,因為數據庫中沒有相對應的類型存儲。Hibernate選擇將集合中的復合類型拆分成多個字段,其他的和普通的集合屬性映射并沒有太大變化。

這里寫圖片描述

只不過對于普通的集合類型映射來說,圖中紅色框中內容僅僅是一個字段,而對于復合類型,由于數據庫中并沒有相對應的類型來存儲,所以就需要拆分成基本的字段類型。

至此,使用注解方法來配置實體類的基本內容已經簡單介紹完了,還有很多相對而言并不常用的基于Hibernate自身的注解并沒有做介紹,待作者深入使用后再做相關補充,總結不到之處,望指出!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容