碼農小汪-Hibernate學習9-hibernate雙向關聯關系注解表示@OneToMany mappedBy @ManyToMany @JoinTable


之前我學習了單向的關聯,現在繼續學習,雙向的關聯。這個關聯關系的理解還是有點復雜,慢慢的理解懂了就好啦!這個過程不是一蹴而就的。我們需要不斷的積累,才可以有所成績的。

年輕人,不要怕~慢慢來

對啦,有的時候我們可能會采用逆向工程產生實體哦。所以我們要看得懂,會修改,會改變

雙向1-N關聯

對於1-N的關聯,Hibernate 推薦使用雙向的關聯,而不要讓1的端(也就是有Set這個集合的那一個)控制關聯的關系。而是使用N的一端控制關聯關系。雙向的1-N和N-1關聯其實就是一回事。兩端都需要增加對關聯屬性的訪問,N的一端增加引用關聯實體的屬性,1的一端增加集合屬性,集合元素為關聯的實體.

Hibernate 同樣對月雙向關聯的映射提供了兩種技術支持:一種是有連接表的,一種是無連接表的。對於大部分來說,對於1-N的雙向關聯我們采用無連接表的策略即可。

無連接的雙向的1-N關聯

無連接的雙向的1-N關聯,N的一端肯定是使用@ManyToOne 我們肯定不會默生的澀,1的一端使用@OneToMany.

底層的數據庫在記錄這種1-N的關聯關系的時候,其實就是我們的外鍵而已,只需要在N的一端增加一個外鍵列就好啦。因此在使用@ManyToOne的同時,還需要使用@JointColumn來映射外鍵列。

對於雙向的1-N關聯的映射,通常不會讓1(set集合)的端控制關系,這樣沒次都需要更新我們的set集合,很不方便的說。而是該由我們的N的一端控制關聯的關系。因此我們使用@OneToMany的時候中應該指明mappedBy (映射由誰控制) mappedBy屬性—–一但為@OneToMany,@ManyToMany指定了這個屬性,則表明當前的實體不能控制關聯的關系。放棄控制關系之后,就不能指明@JoinColumn 或@JointTable 來修飾關聯實體的屬性啦(這兩個實體都是外鍵和第三張表啊來維護關系)

例子:下面的@OneToMany在關聯實體的set集合,指明了mappedBy那么1 的端放棄控制關聯關系,其實這樣也好。每次都更新set集合好累啊。

這個所謂的維護關聯關系,主要是看底層的表中是否有外鍵,如果有外鍵的話,肯定的先有外鍵生成,才可以產生現在的嘛。不能亂了順序澀。

比如 person這個一的端 現在不維護關聯關系了 這么說嘛person的 底層表中沒得關於地址的任何消息所以我可以隨便的保存person實體。而不需要任何的條件。 但是address就不行了,因為現在表中有了person_id這個外鍵。,所以必須等到有了person實體的屬性,才可以讓我的地址產生並且維護他們之間的關系。 后面有例子的。這個只是我自己的看法。以后忘了再來看看當時的心德。

例子~~
Person 1個人 多個地址

@Entity
@Table(name="person_inf")
public class Person
{

@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
// 定義該Person實體所有關聯的Address實體
// 指定mappedBy屬性表明該Person實體不控制關聯關系
//這里的person是address中的person
@OneToMany(targetEntity=Address.class
, mappedBy="person")
private Set<Address> addresses
= new HashSet<>();
...

}

然后Address中只需要增加一個Person person這個屬性。需要使用@ManyToOne @JoinColumn 設置外鍵

繼續看例子

@Entity
@Table(name="address_inf")
public class Address
{

@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
private String addressDetail;
// 定義該Address實體關聯的Person實體
@ManyToOne(targetEntity=Person.class)
// 定義名為person_id外鍵列,該外鍵列引用person_inf表的person_id列。
@JoinColumn(name="person_id" , referencedColumnName="person_id"
, nullable=false)
private Person person;

...

}

不知道你是不是覺得好神奇啊,我們的底層的數據庫都是一樣的都是增加了外鍵,只是在person中增加了個set然后呢,我們就雙向關聯了,任意的獲得對方的屬性啦。開源的力量特別叼

底層數據和我們的1-N的一樣的哦~我是覺得很厲害~不知道你認為呢

Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();
// 創建一個瞬態的Person對象
Person p = new Person();
p.setName("Test");
p.setAge(29);

你說現在我可以保存到數據庫? Person中的set沒有賦值哦。可能你忘記啦,我們表的關系是有address維護的,在address中有個外鍵咯。所以現在我們的person表和address沒有關系。我的person當然可以持久化啦。這個 毫無疑問

// 持久化Person對象(對應於插入主表記錄)
session.save(p);
沒問題

創建address啦

Address a = new Address("遵義市海龍壩");

現在可以保存?當然不可以,我們必須有外鍵呢,外鍵都沒得,所以得設置關聯關系。並且我們還必須是個持久化的對象,不能是瞬態的對象,上面的person是個持久態的,所以可以。為啥瞬態的不行呢?因為我們沒有設置級聯關系,插入的時候不會級聯的操作,這樣的話,外鍵表Id不存在,會報錯的出現異常。你懂了?我也是慢慢的自己分析懂啦

// 先設置Person和Address之間的關聯關系
a.setPerson(p);
// 再持久化Address對象(對應於插入從表記錄)
session.persist(a); //a 作為外鍵必須是持久化對象,不然會報錯的
tx.commit();
HibernateUtil.closeSession();

你懂了吧?這個理解很重要哦,Hibernate的難點,持久化對象,關聯關系。記得大二的時候覺得框架好特別,沒得人帶我飛,自己看這個Hibernate,看的那個各種暈,現在好辣,終於可以懂啦。不錯,說明在進步。

有連接表的雙向1-N關聯

我們的關注底層數據庫以怎么樣的形式呈現給我們呢,因為增加第三張表來維護關系。Person和Address表中都沒有外鍵啦!這樣子不需要擔心保存我們的Person 信息和Address 信息。只是在建立兩者之間的關聯關系的時候,放在第三張表中,要保證我們的外鍵有效,什么是有效呢?address.setPonsen(b) 這種情況下要保證adress 和 b都是有效的,數據庫中存在的,才會插入第三張表中沒得問題。

對於有連接表的1-N,讓N端控制關系的話,1的端無需改變,N的端和單向有連接表的類似

    @Id 
@Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
private String addressDetail;
// 定義該Address實體關聯的Person實體
@ManyToOne(targetEntity=Person.class)
// 映射連接表,指定連接表為person_address
@JoinTable(name="person_address",
// 指定連接表中address_id列參照當前實體對應數據表的address_id主鍵列
joinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id", unique=true),
// 指定連接表中person_id列參照當前實體的關聯實體對應數據表的person_id主鍵列
inverseJoinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id")
)
private Person person;
...
}

person –我們還是不維持關系吧。其實吧,這里誰維持關系都一樣。都是插入第三張表中,性能沒得影響。person中也可以使用@JoinTableb 表名一樣就行啦。不需要指定誰維護,都一樣啊!
因為是第三張表中的哈哈。

@Entity
@Table(name="person_inf")
public class Person
{

@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
// 定義該Person實體所有關聯的Address實體
@OneToMany(targetEntity=Address.class ,mappedBy="person")
這兩種的區別就是把,維護關系不啊~
/*@OneToMany(targetEntity=Address.class)
// 映射連接表,指定連接表為person_address
@JoinTable(name="person_address",
// 指定連接表中person_id列參照當前實體對應數據表的person_id主鍵列
joinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id"),
// 指定連接表中address_id列參照當前實體的關聯實體對應數據表的address_id主鍵列
inverseJoinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id", unique=true)
)*/

private Set<Address> addresses
= new HashSet<>();

看上面的兩種關系都可以,維護維護關系影響不大的,自己看着辦就是啦。因為第三張表啦。對於性能沒影響,不是更新,是插入第三張表。兩者都可以維護關系

看你理解沒理解,不需要維護關系是啥子意思呢?
我們來操作就知道啦

// 創建一個瞬態的Person對象
Person p = new Person();
p.setName("Test");
p.setAge(21);
   // 創建一個瞬態的Address對象
Address a = new Address("遵義市海龍壩");

現在可以a.setPerson? 答案不可以的,第三張表中有兩個外鍵,作為主鍵,引用了我們的Person和Address .現在的Person和address對象都不是持久化對象在數據庫中不存在。不行吧??

session.save(p);
session.save(a)
a.setPerson(p)
.commit()...
這樣子就行啦

下面的也是可以的

a.setPerson(p);
// 持久化Address對象
session.persist(a);
// 持久化Person對象
session.save(p);
tx.commit();

為啥子 可以呢,persist並不是立即轉換為insert語句的,save是立即的。所以這樣子就好像可以啦~兩個外鍵都存在。

雙向的N-N關聯

雙向的N-N 兩端都需要set集合,兩端都需要增加對Set集合的屬性的訪問,這里的Set集合是關聯關系的,不要和以前的不是關聯關系的搞混了。雙向的有太多的可能,所以只能采用連接表來操作啦。來表示實體之間的關系。
雙向的N-N關聯,兩端都要set分別使用@ManyToMany 都要使用@JoinTable 顯示的連接在一起,和剛剛一對N不同的是(1-N增加了unique約束)
需要說明的是,哪端想放棄控制關聯關系,可以使用mappBy。也就不能使用@joinTable啦。其實吧上面都是說過了的。建立第三張表啦。我們兩端都可以隨意的控制關聯關系。重點在於底層的數據庫怎么實現的。理解清楚啦,你就知道現在可以持久化?沒有控制關系誒!

來來例子,雖然都是差不多,但是還是來啊~

@Entity
@Table(name="person_inf")
public class Person
{

@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
@ManyToMany(targetEntity=Address.class)
// 映射連接表,指定連接表的表名為person_address
@JoinTable(name="person_address",
// 映射連接表中名為person_id的外鍵列,
// 該列參照當前實體對應表的person_id主鍵列
joinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id"),
// 映射連接表中名為address_id的外鍵列,
// 該列參數當前實體的關聯實體對應表的address_id主鍵列
inverseJoinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id")
)
private Set<Address> addresses
= new HashSet<>();

一樣的繼續

@Entity
@Table(name="address_inf")
public class Address
{

// 標識屬性
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
// 定義地址詳細信息的成員變量
private String addressDetail;
// 定義該Address實體所有關聯的Person實體
@ManyToMany(targetEntity=Person.class)
// 映射連接表,指定連接表的表名為person_address
@JoinTable(name="person_address",
// 映射連接表中名為address_id的外鍵列,
// 該列參照當前實體對應表的address_id主鍵列
joinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id"),
// 映射連接表中名為person_id的外鍵列,
// 該列參照當前實體對應表的person_id主鍵列
inverseJoinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id")
)
private Set<Person> persons
= new HashSet<>();
...
}

兩面都沒放棄控制權,哈哈。沒必要!就是曉得原理啦,隨意來啊~~
我就是任性

// 創建一個Person對象
Person p = new Person();
    // 持久化Person對象(對應於插入主表記錄)
session.save(p);
    // 創建一個瞬態的Address對象
Address a = new Address("遵義市海龍壩");
// 先設置Person和Address之間的關聯關系
a.getPersons().add(p);
// 再持久化Address對象
session.persist(a);

這上面沒錯的,我先持久化了person,創建關聯關系。在持久化地址。地值在持久化的時候,會先創建插入地址,在插入到關系表中的。
也可以這樣寫
session.save(a);
a.getPersons().add(p);
這樣也是可以的,最后提交的時候就插入到了數據庫中。多自己分析哈,自然的就懂了。

雙向的1-1關聯

基於外鍵的哦
直接的代碼。前面的懂了,這點還不是小意思。你維護不維護實體關系呢?一方維護,一方不維護澀
總有一方要先把實體存到數據庫中,然后另一方才有外鍵存在呢。
有mappedBy的不維護,那么低層的數據表中就不會出現啦,所以,可以自己創建並保存到數據庫,不用去理會這個屬性。

@Entity
@Table(name="person_inf")
public class Person
{

// 標識屬性
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
// 定義該Person實體關聯的Address實體
@OneToOne(targetEntity=Address.class , mappedBy="person")
private Address address;
...
}

所以下面
Person p=new Person();
session.save(p)沒有壓力

繼續地址,這里沒有使用連接表哦。還是有先后順序的。必須先有person在有address;

@Entity
@Table(name="address_inf")
public class Address
{

// 標識屬性
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
// 定義地址詳細信息的成員變量
private String addressDetail;
// 定義該Address實體關聯的Person實體
@OneToOne(targetEntity=Person.class)
// 用於映射person_id外鍵列,參照person_inf表的person_id列
// 指定了unique=true表明是1-1關聯
@JoinColumn(name="person_id" , referencedColumnName="person_id"
, unique=true)
private Person person;
..
}

地層的數據庫中多了個person_id 外鍵列
Address address=new Address();
address.addPerson(持久化的Person);
session.save(address)
沒有問題澀。

下面繼續。有鏈接表的
可以兩端都有JoinTable unique熟悉必須的澀,因為是1-1必須具有唯一性另外一段不想控制關系,可以使用mappedBy澀.簡單搞定

@Entity
@Table(name="person_inf")
public class Person
{

@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
// 定義該Person實體關聯的Address實體
@OneToOne(targetEntity=Address.class)
// 映射底層連接表,表名為person_address
@JoinTable(name="person_address",
// 映射連接表的外鍵列,增加unique=true表明是1-1關聯
joinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id" , unique=true),
// 映射連接表的外鍵列,增加unique=true表明是1-1關聯
inverseJoinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id", unique=true)
)
private Address address;

… 不想貼了


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com