雙向一對多關聯關系
“雙向一對多關聯關系”等同於“雙向多對一關聯關系”: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 }