Hibernate框架學(xué)習(xí)之注解配置關(guān)系映射

?????上篇文章我們通過注解對(duì)映射了單個(gè)實(shí)體類,但是具體項(xiàng)目中往往實(shí)體類之間又是相互關(guān)聯(lián)的,本篇文章就是從實(shí)體類之間存在的不同關(guān)聯(lián)角度,具體學(xué)習(xí)下如何映射他們之間的關(guān)聯(lián),主要涉及內(nèi)容如下:

  • 單向的一對(duì)一關(guān)聯(lián)關(guān)系映射
  • 單向的多對(duì)一的關(guān)聯(lián)關(guān)系映射
  • 單向的一對(duì)多的關(guān)聯(lián)關(guān)系映射
  • 單向的多對(duì)多的關(guān)聯(lián)關(guān)系映射
  • 雙向的一對(duì)一關(guān)聯(lián)關(guān)系映射
  • 雙向的一對(duì)多關(guān)聯(lián)關(guān)系映射
  • 雙向的多對(duì)多關(guān)聯(lián)關(guān)系映射

一、單向的一對(duì)一關(guān)聯(lián)關(guān)系映射
首先,我們需要知道什么樣的兩張表具有一對(duì)一的關(guān)聯(lián)關(guān)系。

這里寫圖片描述

這就是一個(gè)典型的單向的一對(duì)一的關(guān)聯(lián)關(guān)系,所謂的一對(duì)一其實(shí)就是指,主表中的一條記錄唯一的對(duì)應(yīng)于從表中的一條記錄。但具體到我們的實(shí)體類中又該如何來寫呢?我們先看一個(gè)完整映射代碼,然后逐漸解釋其中的相關(guān)注解。

//定義實(shí)體類userinfo
@Entity
@Table(name = "userinfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int user_id;
    private String name;
    private int age;
    
    @OneToOne(targetEntity = UserCode.class)
    @JoinColumn(name = "code",referencedColumnName = "code_id",unique = true)
    private UserCode userCode;
    //省略getter,setter方法
}
//定義實(shí)體類usercode
@Entity
@Table(name = "code")
public class UserCode {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int code_id;
    private String code;
    //省略getter,setter方法
}

因?yàn)槭菃蜗虻囊粚?duì)一,所以我們的usercode表并不存在外鍵列可以直接訪問到userinfo表,所以它的實(shí)體類配置沒什么特殊的地方。而userinfo實(shí)體類定義了一個(gè)UserCode 類型的屬性,當(dāng)我們使用hibernate進(jìn)行插入或者返回?cái)?shù)據(jù)時(shí)候,usercode表中對(duì)應(yīng)的記錄則會(huì)被裝在在這個(gè)屬性中,當(dāng)然,我們也通過它配置外鍵關(guān)聯(lián)關(guān)系。

@OneToOne注解指定這是一個(gè)一對(duì)一的關(guān)聯(lián)關(guān)系,targetEntity 指定了被關(guān)聯(lián)的實(shí)體類類型。@JoinColumn用于配置外鍵列,name屬性用于指定外鍵列的列名,Hibernate將會(huì)在userinfo表中增加一個(gè)字段用做外鍵列。referencedColumnName 屬性用于指定該外鍵列用于參照的表字段,這里我們參照的是usercode表的主鍵。由于是一對(duì)一,所以要求外鍵列不能重復(fù),指定unique唯一約束即可。

對(duì)比著表中的各個(gè)字段,再次體會(huì)下上述注解中的屬性的各個(gè)值的意義。


這里寫圖片描述

二、單向的多對(duì)一的關(guān)聯(lián)關(guān)系映射
依然,在詳細(xì)學(xué)習(xí)之前,先看看什么樣的兩張表構(gòu)成多對(duì)一的關(guān)系。

這里寫圖片描述

像這種,userinfo表中多條不同的記錄對(duì)應(yīng)于usersex表中的一條記錄的情況,我們稱作多對(duì)一的關(guān)聯(lián)關(guān)系。其中,多的一方設(shè)有外鍵列,掌控著關(guān)系的維護(hù)。看代碼:

//定義usersex實(shí)體類
@Entity
@Table(name = "userSex")
public class UserSex {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int sex_id;
    private String sex;
    //省略getter,setter方法
}
//定義userinfo實(shí)體類
@Entity
@Table(name = "userinfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int uid;
    private String name;
    private int age;

    @ManyToOne(targetEntity = UserSex.class)
    @JoinColumn(name = "sex",referencedColumnName = "sex_id")
    private UserSex userSex;
    //省略getter,setter方法
}

同樣,@ManyToOne指定這是個(gè)多對(duì)一關(guān)系,并通過targetEntity 屬性指定被關(guān)聯(lián)的實(shí)體類型。@JoinColumn依然用于配置外鍵列。

對(duì)比著表中的各個(gè)字段,再次體會(huì)下上述注解中的屬性的各個(gè)值的意義。


這里寫圖片描述

三、單向的一對(duì)多的關(guān)聯(lián)關(guān)系映射
單向的一對(duì)多和單向的多對(duì)一是完全不同的兩種表間關(guān)系。雖然兩張表看起來是沒什么太大差別,但是關(guān)系的維護(hù)方確實(shí)截然相反的。這種情況下,兩張表的關(guān)系則由一的一方進(jìn)行維護(hù),所以在一的一端需要定義一個(gè)集合屬性用于映射多的一端的記錄集合,看代碼:

//定義一的一端的實(shí)體類
@Entity
@Table(name = "userSex")
public class UserSex {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int sex_id;
    private String sex;

    @OneToMany(targetEntity = UserInfo.class)
    @JoinColumn(name = "sex",referencedColumnName = "sex_id")
    private Set users;
    //省略getter,setter方法
}
//定義多的一端的實(shí)體類
@Entity
@Table(name = "userinfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int uid;
    private String name;
    private int age;
    //省略getter,setter方法
}

其中,@OneToMany指定了兩個(gè)表之間的是一種一對(duì)多的關(guān)聯(lián)關(guān)系,targetEntity 屬性指定被關(guān)聯(lián)的實(shí)體類類型。這里的@JoinColumn是不一樣的,它將生成一個(gè)外鍵字段,但不是生成在本實(shí)體類所代表的數(shù)據(jù)表中,而是生成在被關(guān)聯(lián)的數(shù)據(jù)表中。name屬性指定了外鍵字段的字段名稱,referencedColumnName屬性指定了該外鍵字段的值依賴于本表的那個(gè)字段(我們這里讓他依賴于userSex的主鍵)。

實(shí)際上一對(duì)多就是多對(duì)一的一個(gè)逆向的關(guān)聯(lián)關(guān)系,但是兩張表依然是通過一個(gè)外鍵列來維系,只不過這個(gè)外鍵列由誰生成的有點(diǎn)不同。具體的表結(jié)構(gòu)此處不再貼出,我們通過插入數(shù)據(jù)來感受下一對(duì)多的關(guān)聯(lián)關(guān)系表。

UserInfo user1 = new UserInfo("a",1);
UserInfo user2 = new UserInfo("b",55);
UserInfo user3 = new UserInfo("c",4);
UserInfo user4 = new UserInfo("d",3);

Set<UserInfo> sets = new HashSet<UserInfo>();
sets.add(user1);
sets.add(user2);
sets.add(user3);
sets.add(user4);

UserSex userSex = new UserSex();
userSex.setSex("男");
userSex.setUsers(sets);

session.save(user1);
session.save(user2);
session.save(user3);
session.save(user4);

session.save(userSex);

當(dāng)我們執(zhí)行上述程序的時(shí)候,hibernate首先會(huì)為我們插入四條userinfo記錄到userinfo表中(其中的外鍵字段為空),然后插入一條記錄到usersex表中,在這之后,hibernate將根據(jù)set集合中的元素依次執(zhí)行這么一條SQL語句:

update userinfo set sex=? where uid=?

顯然,根據(jù)集合中每個(gè)元素的id值定位userinfo表,并將這些元素的外鍵字段同一賦值為當(dāng)前usersex實(shí)例的主鍵值。這樣兩張表就形成了對(duì)應(yīng)的關(guān)系了。當(dāng)然,當(dāng)我們想要取出一條usersex實(shí)例時(shí)候,hibernate也會(huì)拿該實(shí)例的主鍵值去搜索userinfo表,并將匹配的記錄裝載到set集合中。

不過這種由一的一端管理關(guān)聯(lián)關(guān)系的情況有點(diǎn)反常規(guī)邏輯,因此不建議用一的一端管理整個(gè)關(guān)聯(lián)關(guān)系。

四、單向的多對(duì)多的關(guān)聯(lián)關(guān)系映射
對(duì)于單向的多對(duì)多關(guān)聯(lián)關(guān)系,我們無法使用外鍵列進(jìn)行管理。所以,一般會(huì)增設(shè)一張輔助表來維系兩張表之間的關(guān)聯(lián)關(guān)系,舉個(gè)例子:一個(gè)人可以有多個(gè)興趣愛好,一個(gè)興趣愛好也可以對(duì)應(yīng)多個(gè)人,我可以獲取到某個(gè)人所有興趣愛好,也可以獲取具有相同興趣愛好的所有人。如果僅僅使用兩張表來描述這種關(guān)聯(lián)關(guān)系的話,根本就無法描述,不信你可以試試,即便可以實(shí)現(xiàn),那種表結(jié)構(gòu)也是極其復(fù)雜冗余的。目前最好的策略是引入第三方表來維系兩張表之間的多對(duì)多關(guān)聯(lián)。

這里寫圖片描述

這樣的表結(jié)構(gòu)是清晰的,也是易于維護(hù)的。下面看看代碼實(shí)現(xiàn):

//定義hobby實(shí)體類
@Entity
@Table(name = "hobby")
public class Hobby {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int hobby_id;
    private String hobby;
    //省略getter,setter方法
}
//定義實(shí)體類userinfo
@Entity
@Table(name = "userinfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int user_id;
    private String name;
    private int age;

    @ManyToMany(targetEntity = Hobby.class)
    @JoinTable(name = "connectTable",
            joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "hobbyid",referencedColumnName = "hobbyid")
    )
    private Set sets;
    //省略getter,setter方法
}

多對(duì)多的關(guān)聯(lián)映射需要使用到一個(gè)注解@JoinTable,該注解用于指定新生成的連接表的相關(guān)信息。name 屬性指定表名,joinColumns 配置外鍵列及其依賴的屬性字段,我們這里在新表中指定一列名為user_id并且依賴于userinfo實(shí)體的主鍵字段的值,inverseJoinColumns 用于指定關(guān)聯(lián)的實(shí)體類的外鍵列,我們這里在新表中會(huì)生成一列名hobbyid并依賴Hobby實(shí)體類的主鍵值。

當(dāng)我們插入數(shù)據(jù)的時(shí)候,會(huì)首先分別插入兩張表的記錄,然后會(huì)根據(jù)userinfo表中的集合屬性中的元素向連接表中進(jìn)行插入。返回?cái)?shù)據(jù)也是類似的。

五、雙向的一對(duì)一的關(guān)聯(lián)關(guān)系映射
其實(shí)本質(zhì)上看,單向的關(guān)聯(lián)關(guān)系和雙向的關(guān)聯(lián)關(guān)系的區(qū)別在于,單向的關(guān)系中,只有一方存在對(duì)另一方的引用,也就是可以通過外鍵列指向另一方,而被引用的一方并不具備指向別人的外鍵列,所以整個(gè)關(guān)系都只有一方在維護(hù)。而雙向的關(guān)系則是兩方都具備關(guān)系維護(hù)的能力,能夠相互訪問。我們看看實(shí)體類的映射:

//定義userinfo實(shí)體類
@Entity
@Table(name = "userinfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int user_id;
    private String name;
    private int age;

    @OneToOne(targetEntity = UserCode.class)
    @JoinColumn(name = "code",referencedColumnName = "code_id",unique = true)
    private UserCode userCode;
    //省略getter,setter方法
}
//定義usercode實(shí)體類
@Entity
@Table(name = "code")
public class UserCode {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int code_id;
    private String code;

    @OneToOne(targetEntity = UserInfo.class,mappedBy = "userCode")
    private UserInfo userInfo;
    //省略getter,setter方法
}

映射雙向一對(duì)一關(guān)系的時(shí)候,需要在兩端都使用@OneToOne修飾,我們?cè)趗serinfo端增加了一個(gè)外鍵列并指向usercode的主鍵。往往兩張表只要有一方維護(hù)著關(guān)系就行了,不建議兩方同時(shí)維護(hù)著關(guān)系,那樣會(huì)造成性能上的損失,我們指定mappedBy 屬性的值來告訴Hibernate,usercode端不打算維護(hù)關(guān)系。當(dāng)我們指定了雙向的關(guān)聯(lián)關(guān)系之后,兩方都存在對(duì)方的引用了,實(shí)現(xiàn)了互訪的能力。

有人可能會(huì)有疑問,usercode一端放棄對(duì)關(guān)系的管理沒有設(shè)置外鍵列,那么我們是如何通過usercode獲得userinfo的引用呢?答案是:

//從usercode查到userinfo表的引用
UserCode userCode = (UserCode) session.get(UserCode.class,1);
這里寫圖片描述

hibernate通過左連接將根據(jù)外鍵列的值和usercode表的主鍵值連接了兩張表,于是我們可以通過usercode的主鍵一次性查到兩張表對(duì)應(yīng)的記錄,最后為我們返回相應(yīng)的實(shí)例。

而如果想要通過userinfo表查詢到usercode表的引用相對(duì)容易些,因?yàn)閡serinfo表中有一個(gè)外鍵列可以使用。查兩次表即可。

六、雙向的一對(duì)多的關(guān)聯(lián)關(guān)系映射
其實(shí)雙向的一對(duì)多和雙向的多對(duì)一是同一種關(guān)聯(lián)關(guān)系,只是主導(dǎo)關(guān)系的人不一樣而已。看代碼:

//定義userinfo實(shí)體類
@Entity
@Table(name = "userinfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int uid;
    private String name;
    private int age;

    @ManyToOne(targetEntity = UserSex.class)
    @JoinColumn(name = "sex_id",referencedColumnName = "sex_id",nullable = false)
    private UserSex userSex;
    //省略getter,setter方法
}
//定義usersex實(shí)體類
@Entity
@Table(name = "userSex")
public class UserSex {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int sex_id;
    private String sex;

    @OneToMany(targetEntity = UserInfo.class,mappedBy = "userSex")
    private Set users;
    //省略getter,setter方法
}

一的一端使用@OneToMany修飾并放棄對(duì)關(guān)系的維護(hù),多的一端使用@ManyToOne修飾,并增加外鍵列指向usersex表的主鍵列。其實(shí)和我們介紹的單向多對(duì)一基本一樣,只是此處的一的一端增加了一個(gè)一對(duì)多的映射,增加了對(duì)userinfo表的一個(gè)引用而已。

對(duì)于我們從多的一端訪問一的一端直接利用的外鍵列進(jìn)行訪問,從一的一端對(duì)多的一端的訪問具體會(huì)生成以下兩條SQL語句:

這里寫圖片描述

先根據(jù)usersex的主鍵值查一次usersex表,再通過usersex的主鍵值去查一次userinfo表,獲取的所有的userinfo記錄都會(huì)被注入到usersex的集合屬性中。

七、雙向的多對(duì)多的關(guān)聯(lián)關(guān)系映射
雙向的多對(duì)多關(guān)系關(guān)聯(lián)的映射依然需要通過第三張輔助表來進(jìn)行連接。依然使用我們上述的userinfo和hobby舉例:

//定義實(shí)體類userinfo
@Entity
@Table(name = "userinfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int user_id;
    private String name;
    private int age;

    @ManyToMany(targetEntity = Hobby.class)
    @JoinTable(name = "connectTable",
            joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "hobby_id",referencedColumnName = "hobby_id")
    )
    private Set sets = new HashSet();
    //省略getter,setter方法
}
//定義實(shí)體類hobby
@Entity
@Table(name = "hobby")
public class Hobby {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int hobby_id;
    private String hobby;

    @ManyToMany(targetEntity = UserInfo.class)
    @JoinTable(name = "connectTable",
        joinColumns = @JoinColumn(name = "hobby_id",referencedColumnName = "hobby_id"),
        inverseJoinColumns = @JoinColumn(name = "user_id",referencedColumnName = "user_id")
    )
    private Set sets = new HashSet();
    //省略getter,setter方法
}

多對(duì)多的兩端都需要指定連接表的信息,但配置的是同一張表的信息,基本沒什么變化。這些注解也是我們介紹過的,此處不再贅述。比如我們想要獲取一個(gè)userinfo實(shí)例,那么hibernate會(huì)先根據(jù)指定的主鍵值查一次userinfo表,然后當(dāng)需要用到usersex表的相關(guān)信息的時(shí)候,hibernate會(huì)拿userinfo的主鍵值再去查一次connect連接表,并將查到的usersex實(shí)例集注入userinfo的集合屬性中。

綜上,我們介紹了關(guān)系型數(shù)據(jù)庫中常見的幾種關(guān)聯(lián)關(guān)系,并介紹了Hibernate是如何利用注解對(duì)實(shí)體類進(jìn)行映射的。總的來說,單向的關(guān)聯(lián)關(guān)系和雙向的關(guān)聯(lián)關(guān)系有一個(gè)最本質(zhì)的區(qū)別,具有雙向關(guān)聯(lián)關(guān)系的兩張表,各自都存在對(duì)對(duì)方的引用,也就是說可以互相訪問的。而單向的關(guān)聯(lián)關(guān)系則永遠(yuǎn)只有一方可以訪問到另一方。

當(dāng)讀者在實(shí)際的項(xiàng)目開發(fā)中使用到這些關(guān)聯(lián)關(guān)系的時(shí)候,想必對(duì)于Hibernate的映射操作會(huì)有更加深刻的認(rèn)識(shí)。總結(jié)不到之處,望指出!

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

推薦閱讀更多精彩內(nèi)容