1. 程式人生 > >Hibernate框架學習之註解配置關系映射

Hibernate框架學習之註解配置關系映射

target 列名 獲取 fonts 查詢 conn unique strategy code

?????上篇文章我們通過註解對映射了單個實體類,但是具體項目中往往實體類之間又是相互關聯的,本篇文章就是從實體類之間存在的不同關聯角度,具體學習下如何映射他們之間的關聯,主要涉及內容如下:

  • 單向的一對一關聯關系映射
  • 單向的多對一的關聯關系映射
  • 單向的一對多的關聯關系映射
  • 單向的多對多的關聯關系映射
  • 雙向的一對一關聯關系映射
  • 雙向的一對多關聯關系映射
  • 雙向的多對多關聯關系映射

一、單向的一對一關聯關系映射
首先,我們需要知道什麽樣的兩張表具有一對一的關聯關系。

技術分享

這就是一個典型的單向的一對一的關聯關系,所謂的一對一其實就是指,主表中的一條記錄唯一的對應於從表中的一條記錄。但具體到我們的實體類中又該如何來寫呢?我們先看一個完整映射代碼,然後逐漸解釋其中的相關註解。

//定義實體類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方法
}
//定義實體類usercode
@Entity
@Table(name = "code")
public class UserCode {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int code_id;
    private String code;
    //省略getter,setter方法
}

因為是單向的一對一,所以我們的usercode表並不存在外鍵列可以直接訪問到userinfo表,所以它的實體類配置沒什麽特殊的地方。而userinfo實體類定義了一個UserCode 類型的屬性,當我們使用hibernate進行插入或者返回數據時候,usercode表中對應的記錄則會被裝在在這個屬性中,當然,我們也通過它配置外鍵關聯關系。

@OneToOne註解指定這是一個一對一的關聯關系,targetEntity 指定了被關聯的實體類類型。@JoinColumn用於配置外鍵列,name屬性用於指定外鍵列的列名,Hibernate將會在userinfo表中增加一個字段用做外鍵列。referencedColumnName 屬性用於指定該外鍵列用於參照的表字段,這裏我們參照的是usercode表的主鍵。由於是一對一,所以要求外鍵列不能重復,指定unique唯一約束即可。

對比著表中的各個字段,再次體會下上述註解中的屬性的各個值的意義。
技術分享

二、單向的多對一的關聯關系映射
依然,在詳細學習之前,先看看什麽樣的兩張表構成多對一的關系。

技術分享

像這種,userinfo表中多條不同的記錄對應於usersex表中的一條記錄的情況,我們稱作多對一的關聯關系。其中,多的一方設有外鍵列,掌控著關系的維護。看代碼:

//定義usersex實體類
@Entity
@Table(name = "userSex")
public class UserSex {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int sex_id;
    private String sex;
    //省略getter,setter方法
}
//定義userinfo實體類
@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指定這是個多對一關系,並通過targetEntity 屬性指定被關聯的實體類型。@JoinColumn依然用於配置外鍵列。

對比著表中的各個字段,再次體會下上述註解中的屬性的各個值的意義。
技術分享

三、單向的一對多的關聯關系映射
單向的一對多和單向的多對一是完全不同的兩種表間關系。雖然兩張表看起來是沒什麽太大差別,但是關系的維護方確實截然相反的。這種情況下,兩張表的關系則由一的一方進行維護,所以在一的一端需要定義一個集合屬性用於映射多的一端的記錄集合,看代碼:

//定義一的一端的實體類
@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方法
}
//定義多的一端的實體類
@Entity
@Table(name = "userinfo")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int uid;
    private String name;
    private int age;
    //省略getter,setter方法
}

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

實際上一對多就是多對一的一個逆向的關聯關系,但是兩張表依然是通過一個外鍵列來維系,只不過這個外鍵列由誰生成的有點不同。具體的表結構此處不再貼出,我們通過插入數據來感受下一對多的關聯關系表。

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);

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

update userinfo set sex=? where uid=?

顯然,根據集合中每個元素的id值定位userinfo表,並將這些元素的外鍵字段同一賦值為當前usersex實例的主鍵值。這樣兩張表就形成了對應的關系了。當然,當我們想要取出一條usersex實例時候,hibernate也會拿該實例的主鍵值去搜索userinfo表,並將匹配的記錄裝載到set集合中。

不過這種由一的一端管理關聯關系的情況有點反常規邏輯,因此不建議用一的一端管理整個關聯關系。

四、單向的多對多的關聯關系映射
對於單向的多對多關聯關系,我們無法使用外鍵列進行管理。所以,一般會增設一張輔助表來維系兩張表之間的關聯關系,舉個例子:一個人可以有多個興趣愛好,一個興趣愛好也可以對應多個人,我可以獲取到某個人所有興趣愛好,也可以獲取具有相同興趣愛好的所有人。如果僅僅使用兩張表來描述這種關聯關系的話,根本就無法描述,不信你可以試試,即便可以實現,那種表結構也是極其復雜冗余的。目前最好的策略是引入第三方表來維系兩張表之間的多對多關聯。

技術分享

這樣的表結構是清晰的,也是易於維護的。下面看看代碼實現:

//定義hobby實體類
@Entity
@Table(name = "hobby")
public class Hobby {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int hobby_id;
    private String hobby;
    //省略getter,setter方法
}
//定義實體類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方法
}

多對多的關聯映射需要使用到一個註解@JoinTable,該註解用於指定新生成的連接表的相關信息。name 屬性指定表名,joinColumns 配置外鍵列及其依賴的屬性字段,我們這裏在新表中指定一列名為user_id並且依賴於userinfo實體的主鍵字段的值,inverseJoinColumns 用於指定關聯的實體類的外鍵列,我們這裏在新表中會生成一列名hobbyid並依賴Hobby實體類的主鍵值。

當我們插入數據的時候,會首先分別插入兩張表的記錄,然後會根據userinfo表中的集合屬性中的元素向連接表中進行插入。返回數據也是類似的。

五、雙向的一對一的關聯關系映射
其實本質上看,單向的關聯關系和雙向的關聯關系的區別在於,單向的關系中,只有一方存在對另一方的引用,也就是可以通過外鍵列指向另一方,而被引用的一方並不具備指向別人的外鍵列,所以整個關系都只有一方在維護。而雙向的關系則是兩方都具備關系維護的能力,能夠相互訪問。我們看看實體類的映射:

//定義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方法
}
//定義usercode實體類
@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方法
}

映射雙向一對一關系的時候,需要在兩端都使用@OneToOne修飾,我們在userinfo端增加了一個外鍵列並指向usercode的主鍵。往往兩張表只要有一方維護著關系就行了,不建議兩方同時維護著關系,那樣會造成性能上的損失,我們指定mappedBy 屬性的值來告訴Hibernate,usercode端不打算維護關系。當我們指定了雙向的關聯關系之後,兩方都存在對方的引用了,實現了互訪的能力。

有人可能會有疑問,usercode一端放棄對關系的管理沒有設置外鍵列,那麽我們是如何通過usercode獲得userinfo的引用呢?答案是:

//從usercode查到userinfo表的引用
UserCode userCode = (UserCode) session.get(UserCode.class,1);

技術分享

hibernate通過左連接將根據外鍵列的值和usercode表的主鍵值連接了兩張表,於是我們可以通過usercode的主鍵一次性查到兩張表對應的記錄,最後為我們返回相應的實例。

而如果想要通過userinfo表查詢到usercode表的引用相對容易些,因為userinfo表中有一個外鍵列可以使用。查兩次表即可。

六、雙向的一對多的關聯關系映射
其實雙向的一對多和雙向的多對一是同一種關聯關系,只是主導關系的人不一樣而已。看代碼:

//定義userinfo實體類
@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實體類
@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修飾並放棄對關系的維護,多的一端使用@ManyToOne修飾,並增加外鍵列指向usersex表的主鍵列。其實和我們介紹的單向多對一基本一樣,只是此處的一的一端增加了一個一對多的映射,增加了對userinfo表的一個引用而已。

對於我們從多的一端訪問一的一端直接利用的外鍵列進行訪問,從一的一端對多的一端的訪問具體會生成以下兩條SQL語句:

技術分享

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

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

//定義實體類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方法
}
//定義實體類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方法
}

多對多的兩端都需要指定連接表的信息,但配置的是同一張表的信息,基本沒什麽變化。這些註解也是我們介紹過的,此處不再贅述。比如我們想要獲取一個userinfo實例,那麽hibernate會先根據指定的主鍵值查一次userinfo表,然後當需要用到usersex表的相關信息的時候,hibernate會拿userinfo的主鍵值再去查一次connect連接表,並將查到的usersex實例集註入userinfo的集合屬性中。

綜上,我們介紹了關系型數據庫中常見的幾種關聯關系,並介紹了Hibernate是如何利用註解對實體類進行映射的。總的來說,單向的關聯關系和雙向的關聯關系有一個最本質的區別,具有雙向關聯關系的兩張表,各自都存在對對方的引用,也就是說可以互相訪問的。而單向的關聯關系則永遠只有一方可以訪問到另一方。

當讀者在實際的項目開發中使用到這些關聯關系的時候,想必對於Hibernate的映射操作會有更加深刻的認識。總結不到之處,望指出!

Hibernate框架學習之註解配置關系映射