8、雙向一對多的關聯關系(等同於雙向多對一。1的一方有對n的一方的集合的引用,同時n的一方有對1的一方的引用)


 

雙向一對多關聯關系

“雙向一對多關聯關系”等同於“雙向多對一關聯關系”:1的一方有對n的一方的集合的引用,同時n的一方有對1的一方的引用。

 

還是用客戶Customer和訂單Order來解釋:

“一對多”的物理意義:一個客戶可以有多個訂單,某個訂單只能歸宿於一個客戶。

“雙向”的物理意義:客戶知道自己有哪些訂單,訂單也知道自己歸宿於哪個客戶。也就是說,通過客戶對象可以檢索到其擁有哪些訂單;同時,通過訂單也可以查找到其對應的客戶信息。這是符合我們業務邏輯需求。

 

到現在為止(結合前面兩節的闡述)我們可以很深刻的理解“雙向”、“單向”、“一對多”、“多對一”這四個詞語了:

①、“一對多”講的是一個實體類中是否包含有對另外一個實體類的集合的引用。

②、“多對一”包含兩層含義:a、一個實體類Ea是否包含有對另外一個實體類Eb的引用;b、是否允許實體類Ea的多個對象{ea1, ea2, ea3,...}同時對實體類Eb的某個對象eb有引用關系(如果不允許“多個對一個”,那么就是后面要講的“一對一關聯關系”),如下Figure_1所示:

  Figure_1. 允許多對一關聯

  

③、“雙向”包含兩個缺一不可的層面:a、1的一方有對n的一方的集合的引用;b、同時,n的一方也有對1的一方的對象的引用;也就是同時滿足①、②兩點。

④、“單向”就是③中闡述的兩個層面只出現一個。也就是只滿足①、②兩點中的一點。

:我們上面①~④所講的集合實體類中的集合,同時集合之上還要有映射注解(或映射配置文件)進行相關的關聯映射。如果單單只是一個集合,卻沒有表示映射關系的注解或配置文件,那么這個集合就不是我們映射層面上的“集合”。實體類中的對象也是一樣的道理。

 

下面我們通過Customer和Order的關系來印證我們上面的這種理解:

List_1. Customer實體類(有對Order的集合的引用)
 1 @Table(name=“t_double_one2many_customer”)
 2 @Entity
 3 public class Customer2 {
 4 
 5     private Integer id;
 6     private String lastName;
 7 
 8     private String email;
 9     private int age;
10     
11     private Date birthday;
12     
13     private Date createdTime;
14     
15     // 有對Order2的集合的引用 16     //(這個引用還要被注解表示為一種映射關系才行)
17     private Set<Order2> orders = new HashSet<Order2>(); 18 
19     // 省略getter、setter方法
20 }
List_2. Order實體類(有對Customer的實體的對象的引用)
 1 @Table(name=“t_double_one2many_order”)
 2 @Entity
 3 public class Order2 {
 4     
 5     private Integer id;
 6     private String orderName;
 7     
 8     //n的一方有對1的一方的對象的引用  9     //①要有映射注解表明為映射;②、允許多對一,否則就是一對一
10     private Customer2 customer; 11 
12         // 省略getter、setter方法
13 }

 

從Customer實體類和Order實體類的屬性定義我們可以得出下面的東西:

1、滿足上面③的要求,所以是“雙向”。

2、同時還滿足①、②兩點要求(這里也可以推導出“雙向”)

從以上可以看出,Customer和Order是“雙向一對多”或“雙向多對一”關聯關系。

 

雙向關聯關系的默認策略和兩個單項是一致的:

1、對1的一端的集合引用的檢索采用采用延遲加載方式;對n的一端的對象引用的檢索采用立即加載方式;可以通過設置@ManyToOne或@OneToMany的fetch屬性來修改默認策略。

2、可以自由的刪除n的一方的某個對象;但是,對1的一方的對象而言,如果還有n的一方的某個對象引用它,那么就不能夠刪除1的一方的該對象。可以通過設置@ManyToOne或@OneToMany的cascade屬性來修改默認的刪除策略;

 

配置雙向多對一關聯關系的具體操作:

1、如果雙邊都維護關聯關系:

  ①n的一端做如下配置

  List_3. n的一方的配置
1 @ManyToOne
2 @JoinColumn(name=“CUSTOMER_ID”)
3 public Customer2 getCustomer() {
4     return customer;
5 }

  ②、1的一端做如下配置

  list_4. 1的一方的配置
1 @OneToMany
2 @JoinColumn(name=“CUSTOMER_ID”)
3 public Set<Order2> getOrders() {
4     return orders;
5 }

  要注意的是,兩邊的@JoinColumn的name屬性要一致(這里都是CUSTOMER_ID)。最后建立得到的數據表效果就是在n的一方對應的數據表中有一列外鍵,而1的一方沒有外鍵列(可以想象,1的一方無法放置外鍵列)

  這種雙邊都維護關聯關系的時候,保存對象不可避免的要發送多余的update語句,和單向一對多關聯關系一樣。無論你是先保存1的一端,還是先保存n的一端都無法避免update語句的發送。

 

2、只靠n的一方維護關聯關系(推薦使用):

  在雙向多對一關聯關系中,1的一方沒有必要維護關聯關系,只靠n的一方維護就夠了。這樣做的好處就是保存對象的時候先保存1的一端,后保存n的一端,可以避免發送update語句(和單向多對一關聯關系一樣)。

  具體的配置方法就是:a、n的一方配置如 “List_3. n的一方的配置” 一樣; b、1的一方配置不能使用@JoinColumn(name=“CUSTOMER_ID”)注解(如果有此注解,則會報錯),同時在@OneToMany中配置mappedBy=“customer”屬性指定由n的一方的哪個屬性來維護關聯關系(注意,這里的customer是n一端的對象引用的名稱),如下List_5:

  List_5. 1的一方不維護關聯關系
 1 /**
 2  * 1、雙向1-n關聯的時候,一般1的一方放棄維護關聯關系,而由n的一方維護關聯關系。
 3  *    這樣做的好處就是:先保存1的一端,再保存n的一端的時候不會有多余的update sql語句
 4  * 2、使用@OneToMany(mappedBy=“customer”)來指明由n的一方的哪個屬性來維護關聯關系
 5  * 3、有一個值得注意的地方如果指明了mappedBy=“customer”,那么就不能夠再使用@JoinColumn注解了10  * 
11  */
12 //    @JoinColumn(name=“CUSTOMER_ID”)
13 @OneToMany(mappedBy=“customer”)
14 public Set<Order2> getOrders() {
15     return orders;
16 }

 

說明就創建的兩張數據表而言,“雙邊維護關聯關系”與“單邊維護關聯關系”所創建的數據表沒有區別,都是由n的一方對應的數據表有一個外鍵參照列,而1的一方沒有任何外鍵列。兩張數據表如下:

Figure_2. Customer實體對應的數據表

Figure_3. Order實體對應的數據表

注意這里的“CUSTOMER_ID”是@JoinColumn(name=“CUSTOMER_ID”)中指定的CUSTOMER_ID注意這里的Order實體表中有外鍵列,而Customer實體表中沒有外鍵列。

 

下面是Customer和Order實體類:

List_6. Customer2.java(作為1的一方,有對n的一方的集合的引用,不維護關聯關系)
  1 package com.magicode.jpa.doubl.many2one;
  2 
  3 import java.util.Date;
  4 import java.util.HashSet;
  5 import java.util.Set;
  6 
  7 import javax.persistence.Column;
  8 import javax.persistence.Entity;
  9 import javax.persistence.GeneratedValue;
 10 import javax.persistence.GenerationType;
 11 import javax.persistence.Id;
 12 import javax.persistence.OneToMany;
 13 import javax.persistence.Table;
 14 import javax.persistence.TableGenerator;
 15 import javax.persistence.Temporal;
 16 import javax.persistence.TemporalType;
 17 import javax.persistence.Transient;
 18 
 19 /**
 20  * @Entity 用於注明該類是一個實體類
 21  * @Table(name=“t_customer”) 表明該實體類映射到數據庫的 t_customer 表
 22  */
 23 @Table(name=“t_double_one2many_customer”)
 24 @Entity
 25 public class Customer2 {
 26 
 27     private Integer id;
 28     private String lastName;
 29 
 30     private String email;
 31     private int age;
 32     
 33     private Date birthday;
 34     
 35     private Date createdTime;
 36     
 37     private Set<Order2> orders = new HashSet<Order2>();  38 
 39     @TableGenerator(name=“ID_GENERATOR_2”,
 40             table=“t_id_generator”,
 41             pkColumnName=“PK_NAME”,
 42             pkColumnValue=“seedId_t_customer2”,
 43             valueColumnName=“PK_VALUE”,
 44             allocationSize=20,
 45             initialValue=10
 46             )
 47     @GeneratedValue(strategy=GenerationType.TABLE, generator=“ID_GENERATOR_2”)
 48     @Id
 49     @Column(name=“ID”)
 50     public Integer getId() {
 51         return id;
 52     }
 53     
 54     /**
 55      * 1、雙向1-n關聯的時候,一般1的一方放棄維護關聯關系,而由n的一方維護關聯關系。
 56      *       這樣做的好處就是:先保存1的一端,再保存n的一端的時候不會有多余的update sql語句
 57      * 2、使用@OneToMany(mappedBy=“customer”)來指明由n的一方的哪個屬性來維護關聯關系
 58      * 3、有一個值得注意的地方:如果指明了mappedBy=“customer”,那么久不能夠再使用@JoinColumn注解了。  59      */
 60     //@JoinColumn(name=“CUSTOMER_ID”)
 61     @OneToMany(mappedBy=“customer”)  62     public Set<Order2> getOrders() {
 63         return orders;
 64     }
 65 
 66     public void setOrders(Set<Order2> orders) {
 67         this.orders = orders;
 68     }
 69     
 70     @Column(name=“LAST_NAME”, length=50, nullable=false)
 71     public String getLastName() {
 72         return lastName;
 73     }
 74     
 75     @Column(name=“BIRTHDAY”)
 76     @Temporal(TemporalType.DATE)
 77     public Date getBirthday() {
 78         return birthday;
 79     }
 80 
 81     @Column(name=“CREATED_TIME”, columnDefinition=“DATE”)
 82     public Date getCreatedTime() {
 83         return createdTime;
 84     }
 85     
 86     @Column(name=“EMAIL”,columnDefinition=“TEXT”)
 87     public String getEmail() {
 88         return email;
 89     }
 90     
 91     /*
 92      * 工具方法,不需要映射為數據表的一列
 93      */
 94     @Transient
 95     public String getInfo(){
 96         return “lastName: ” + lastName + “ email: ” + email;
 97     }
 98 
 99     @Column(name=“AGE”)
100     public int getAge() {
101         return age;
102     }
103 
104     @SuppressWarnings(“unused”)
105     private void setId(Integer id) {
106         this.id = id;
107     }
108 
109     public void setLastName(String lastName) {
110         this.lastName = lastName;
111     }
112 
113     public void setEmail(String email) {
114         this.email = email;
115     }
116 
117     public void setAge(int age) {
118         this.age = age;
119     }
120 
121     public void setBirthday(Date birthday) {
122         this.birthday = birthday;
123     }
124 
125     public void setCreatedTime(Date createdTime) {
126         this.createdTime = createdTime;
127     }
128     
129 }
List_7. Order2.java(作為n的一方,有對1的一方的對象的引用,維護關聯關系)
 1 package com.magicode.jpa.doubl.many2one;
 2 
 3 import javax.persistence.Column;
 4 import javax.persistence.Entity;
 5 import javax.persistence.GeneratedValue;
 6 import javax.persistence.GenerationType;
 7 import javax.persistence.Id;
 8 import javax.persistence.JoinColumn;
 9 import javax.persistence.ManyToOne;
10 import javax.persistence.Table;
11 import javax.persistence.TableGenerator;
12 
13 @Table(name=“t_double_one2many_order”)
14 @Entity
15 public class Order2 {
16     
17     private Integer id;
18     private String orderName;
19     
20     private Customer2 customer; 21     
22     @TableGenerator(name=“order_id_generator_2”,
23             table=“t_id_generator”,
24             pkColumnName=“PK_NAME”,
25             pkColumnValue=“seedId_t_order2”,
26             valueColumnName=“PK_VALUE”,
27             initialValue=0,
28             allocationSize=20)
29     @GeneratedValue(generator=“order_id_generator_2”, strategy=GenerationType.TABLE)
30     @Id
31     @Column(name=“ID”)
32     public Integer getId() {
33         return id;
34     }
35 
36     /**
37      * 這里的name=“CUSTOMER_ID”會作為Order對象存放的數據庫表的一個外鍵列 38      * 用於維護關聯關系
39      */
40  @ManyToOne 41     @JoinColumn(name=“CUSTOMER_ID”) 42     public Customer2 getCustomer() {
43         return customer;
44     }
45 
46     public void setCustomer(Customer2 customer) {
47         this.customer = customer;
48     }
49 
50     @Column(name=“ORDER_NAME”)
51     public String getOrderName() {
52         return orderName;
53     }
54     
55     @SuppressWarnings(“unused”)
56     private void setId(Integer id) {
57         this.id = id;
58     }
59     
60     public void setOrderName(String orderName) {
61         this.orderName = orderName;
62     }
63 
64 }
List_8. 測試代碼
  1 package com.magicode.jpa.doubl.many2one;
  2 
  3 import java.util.Date;
  4 
  5 import javax.persistence.EntityManager;
  6 import javax.persistence.EntityManagerFactory;
  7 import javax.persistence.EntityTransaction;
  8 import javax.persistence.Persistence;
  9 
 10 import org.junit.After;
 11 import org.junit.Before;
 12 import org.junit.Test;
 13 
 14 
 15 
 16 public class DoubleMany2OneTest {
 17     
 18     EntityManagerFactory emf = null;
 19     EntityManager em = null;
 20     EntityTransaction transaction = null;
 21     
 22     @Before
 23     public void before(){
 24         emf = Persistence.createEntityManagerFactory(“jpa-1”);
 25         em = emf.createEntityManager();
 26         transaction = em.getTransaction();
 27         transaction.begin();
 28     }
 29     
 30     @After
 31     public void after(){
 32         transaction.commit();
 33         em.close();
 34         emf.close();
 35     }
 36     
 37     @Test
 38     public void testPersist(){
 39         
 40         int i = 1;
 41         
 42         char c = (char) ('A' + i);
 43         String strName = (“ ” + c + c).trim();
 44         int age = 25 + i;
 45         
 46         Customer2 customer = new Customer2();
 47         customer.setAge(age);
 48         customer.setEmail(strName + “@163.com”);
 49         customer.setLastName(strName);
 50         customer.setBirthday(new Date());
 51         customer.setCreatedTime(new Date());
 52         
 53         Order2 order1 = new Order2();
 54         order1.setOrderName(“O-” + strName + “-1”);
 55         
 56         Order2 order2 = new Order2();
 57         order2.setOrderName(“O-” + strName + “-2”);
 58         
 59         //設置關聯關系
 60         customer.getOrders().add(order1);
 61         customer.getOrders().add(order2);
 62         
 63         order1.setCustomer(customer);
 64         order2.setCustomer(customer);
 65         
 66         //持久化操作
 67         /**
 68          * 雙向1-n的關聯關系中,1的一方放棄維護關聯關系,由n的一方維護關聯關系。
 69          * 建議“先保存1的一端,再保存n的一端”,這樣就不會有多余的update sql語句
 70          */
 71         em.persist(customer);
 72         em.persist(order1);
 73         em.persist(order2);
 74     }
 75     
 76     @Test
 77     public void testFind(){
 78         /**
 79          * 雙向多對一在查詢時默認的策略如下:
 80          * 1、檢索1的一方的時候,其包含的對n的集合屬性的檢索默認采用延遲加載,
 81          *     可以設置@OneToMany(fetch=FetchType.EAGER)來修改為立即加載策略;
 82          * 
 83          * 2、檢索n的一方的時候,對其包含的1的一方默認采用立即加載策略,
 84          *     可以設置@ManyToOne(fetch=FetchType.LAZY)來修改為延遲加載策略;
 85          * 
 86          * 很容易記混淆。但是我們可以深入思考一下,這樣做是有道理的:
 87          * ①、1的一方包含了對n的一方的集合屬性,在檢索的時候集合中到底有多少個元素我們根本
 88          * 就不知道,可能是幾個,也可能是1000000個呢!!!如果默認采用立即檢索策略,可以想
 89          * 象后果有多嚴重。
 90          * ②、n的一方包含了1的一方的一個對象,這和一個Integer或者是String類型的對象
 91          * 沒有區別。也不會像集合那樣可能占用巨大的內存資源。
 92          * 
 93          */
 94         Customer2 customer = em.find(Customer2.class, 11);
 95         
 96         System.out.println(“---------”);
 97         System.out.println(customer.getOrders().iterator().next().getOrderName());
 98         
 99         System.out.println(“---------”);
100         Order2 order = em.find(Order2.class, 1);
101         System.out.println(“---------”);
102         System.out.println(order.getCustomer().getEmail());
103     }
104     
105     @Test
106     public void testRemove(){
107         /**
108          * 雙向n-1關聯關系,在默認的情況下,如果1的一方集合中還保存有n的一方的引用,那么是無法刪除1的一方的;
109          * 但是可以任意刪除n的一方。
110          * 可以設置@OneToMany(cascade={CascadeType.REMOVE})來進行級聯刪除:刪除1的同時,把其
111          * 關聯的n的一方同時刪除;
112          */
113 //        Customer2 customer = em.find(Customer2.class, 11);
114 //        em.remove(customer);
115         
116         Order2 order = em.find(Order2.class, 1);
117         em.remove(order);
118     }
119 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM