?????上篇文章我們通過注解對(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é)不到之處,望指出粱腻!