十、多表映射
0、內容補充:數據完整性
作用:防止用戶的誤操作。
實體完整性:主鍵。用於確定表中唯一的一條記錄。
域完整性:表中的字段。
數據類型約束:
非空約束:
唯一約束:
參照完整性:
多表設計:表之間的關系
一對多(用的最多的)
多對多(比較重要)
一對一(實際開發中,根本不用)
1、一對多關系映射(非常重要)
1.1、單向多對一映射
1 /** 2 * 客戶的數據模型 3 * @author zhy 4 * 5 * 一個客戶可以有多個訂單 6 * 多個訂單屬於一個客戶。 7 * 8 * 客戶和訂單之間的關系是一對多 9 */ 10 public class Customer implements Serializable { 11 12 private Integer id; 13 private String name; 14 private Integer age; 15 16 //一對多關系映射:一個客戶可以有多個訂單 17 private Set<Order> orders = new HashSet<Order>(0); 18 19 20 public Integer getId() { 21 return id; 22 } 23 public void setId(Integer id) { 24 this.id = id; 25 } 26 public String getName() { 27 return name; 28 } 29 public void setName(String name) { 30 this.name = name; 31 } 32 public Integer getAge() { 33 return age; 34 } 35 public void setAge(Integer age) { 36 this.age = age; 37 } 38 public Set<Order> getOrders() { 39 return orders; 40 } 41 public void setOrders(Set<Order> orders) { 42 this.orders = orders; 43 } 44 @Override 45 public String toString() { 46 return "Customer [id=" + id + ", name=" + name + ", age=" + age + "]"; 47 } 48 }
1 /** 2 * 訂單的數據模型 3 * @author zhy 4 * 5 * 一個客戶可以有多個訂單 6 * 多個訂單屬於一個客戶。 7 * 8 * 客戶和訂單之間的關系是一對多 9 */ 10 public class Order implements Serializable { 11 12 private Integer id; 13 private String ordernum; 14 private Float money; 15 16 //多對一關系映射:多個訂單屬於一個客戶。 17 private Customer customer; 18 19 20 public Integer getId() { 21 return id; 22 } 23 public void setId(Integer id) { 24 this.id = id; 25 } 26 public String getOrdernum() { 27 return ordernum; 28 } 29 public void setOrdernum(String ordernum) { 30 this.ordernum = ordernum; 31 } 32 public Float getMoney() { 33 return money; 34 } 35 public void setMoney(Float money) { 36 this.money = money; 37 } 38 public Customer getCustomer() { 39 return customer; 40 } 41 public void setCustomer(Customer customer) { 42 this.customer = customer; 43 } 44 @Override 45 public String toString() { 46 return "Order [id=" + id + ", ordernum=" + ordernum + ", money=" + money + "]"; 47 } 48 }
1 <hibernate-mapping package="cn.itcast.domain"> 2 <class name="Customer" table="T_CUSTOMERS"> 3 <id name="id" column="id"> 4 <generator class="native"></generator> 5 </id> 6 <property name="name" column="NAME"></property> 7 <property name="age" column="AGE"></property> 8 <!-- 一對多關系映射: 9 set元素: 10 作用:映射集合元素 11 屬性: 12 name:映射實體類中的集合屬性 13 table:指定對應的表 14 key元素:它是set的子元素 15 作用:就是用於指定外鍵的 16 屬性: 17 column:指定外鍵字段的名稱 18 one-to-many元素:它是set的子元素 19 作用:描述當前實體映射文件和set中指定屬性之間的關系。 20 屬性: 21 class:指定是從表的實體類名稱 22 --> 23 <set name="orders" table="T_ORDERS" cascade="save-update,delete" inverse="true"> 24 <key column="CUSTOMER_ID"></key> 25 <one-to-many class="Order"/> 26 </set> 27 </class> 28 </hibernate-mapping>
1 <hibernate-mapping package="cn.itcast.domain"> 2 <class name="Order" table="T_ORDERS"> 3 <id name="id" column="id"> 4 <generator class="native"></generator> 5 </id> 6 <property name="ordernum" column="ORDERNUM"></property> 7 <property name="money" column="MONEY"></property> 8 9 <!-- 多對一關系映射 10 使用的元素:many-to-one 11 屬性: 12 name:指定的是在實體類中要映射的屬性 13 class:指定該屬性所對應的類 14 column:指定外鍵字段。 15 --> 16 <many-to-one name="customer" class="Customer" column="CUSTOMER_ID" cascade="save-update"></many-to-one> 17 </class> 18 </hibernate-mapping>
a、保存操作
1 /* 2 * 保存操作 3 * 需求: 4 * 保存兩個訂單,同時保存一個客戶 5 * 一定是先保存訂單,再保存客戶 6 * 問題: 7 * 當我們先保存訂單,再保存客戶時,會執行5條SQL語句 8 * Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?) 9 Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?) 10 Hibernate: insert into T_CUSTOMERS (NAME, AGE) values (?, ?) 11 Hibernate: update T_ORDERS set ORDERNUM=?, MONEY=?, CUSTOMER_ID=? where id=? 12 Hibernate: update T_ORDERS set ORDERNUM=?, MONEY=?, CUSTOMER_ID=? where id=? 13 解決辦法: 14 實際上我們只需要三條insert語句就夠了 15 在保存時,先保存主表數據,再保存從表數據 16 */ 17 @Test 18 public void test1(){ 19 //數據准備 20 Customer c1 = new Customer(); 21 c1.setName("test"); 22 c1.setAge(18); 23 24 Order o1 = new Order(); 25 o1.setOrdernum("A001"); 26 o1.setMoney(100f); 27 28 Order o2 = new Order(); 29 o2.setOrdernum("A002"); 30 o2.setMoney(200f); 31 //建立單向多對一關聯關系 32 o1.setCustomer(c1); 33 o2.setCustomer(c1); 34 35 Session s = HibernateUtil.getSession(); 36 Transaction tx = s.beginTransaction(); 37 //保存操作 38 s.save(c1); 39 s.save(o1); 40 s.save(o2); 41 42 tx.commit(); 43 s.close(); 44 }
b、查詢操作
1 @Test 2 public void test2(){ 3 //數據准備 4 Customer c1 = new Customer();//臨時態 5 c1.setName("test2"); 6 c1.setAge(28); 7 8 Session s = HibernateUtil.getSession(); 9 Transaction tx = s.beginTransaction(); 10 //查詢id為1的訂單 11 Order o1 = s.get(Order.class, 1);//持久態 12 tx.commit(); 13 s.close(); 14 }
c、持久態引用臨時態報錯
1 /* 2 * 更新操作 3 * 需求: 4 * 先創建一個訂單,然后查詢出來一個客戶。 5 * 建立客戶和新訂單的關聯關系。 6 * 更新客戶 7 * 問題: 8 * 一個持久態對象,關聯了一個臨時態的對象。 9 * 解決辦法: 10 * 配置級聯保存更新 11 * <set name="orders" table="T_ORDERS" cascade="save-update"> 12 */ 13 @Test 14 public void test2(){ 15 16 //創建一個新的訂單 17 Order o1 = new Order();//臨時態 18 o1.setOrdernum("A003"); 19 o1.setMoney(100f); 20 21 Session s = HibernateUtil.getSession(); 22 Transaction tx = s.beginTransaction(); 23 //查詢一個客戶 24 Customer c1 = s.get(Customer.class, 1);//持久態 25 //建立雙向關聯關系 26 c1.getOrders().add(o1); 27 o1.setCustomer(c1); 28 //更新操作 29 s.update(c1); 30 31 tx.commit(); 32 s.close(); 33 }
d、級聯保存和更新
1 /* 2 * 3 * 更新操作 4 * 需求: 5 * 創建一個新的客戶,查詢出來一個訂單。把新客戶和查詢的訂單建立關聯關系。 6 * 然后更新訂單 7 * 問題: 8 * 一個持久態對象,關聯了一個臨時態對象。會報錯。 9 * 解決辦法: 10 * 思路:在更新之前,先把臨時態對象,轉成持久態。(先執行保存,再執行更新) 11 * 執行級聯保存更新。 12 * 在配置文件中配置:要想級聯誰,就在對應的映射屬性上配置 13 * cascade屬性:就是用於配置級聯操作的 14 * <many-to-one name="customer" class="Customer" column="CUSTOMER_ID" cascade="save-update"> 15 */ 16 @Test 17 public void test2(){ 18 //數據准備 19 Customer c1 = new Customer();//臨時態 20 c1.setName("test2"); 21 c1.setAge(28); 22 23 Session s = HibernateUtil.getSession(); 24 Transaction tx = s.beginTransaction(); 25 //查詢id為1的訂單 26 Order o1 = s.get(Order.class, 1);//持久態 27 //建立訂單和客戶的單向多對一關聯關系 28 o1.setCustomer(c1); 29 //更新訂單 30 s.update(o1); 31 tx.commit(); 32 s.close(); 33 }
1.2、雙向關聯映射
注意事項:
Hibernate要求在持久化類中定義集合屬性時,必須把屬性聲明為接口類型,如Set、Map、List.聲明接口類型可提高持久化類的透明性。(與延遲加載有關)
通常在定義集合屬性時,直接初始化為一個實現類的實例。可避免空指針異常。
a、雙向關聯關系保存操作
1 /* 2 * 保存操作 3 * 需求: 4 * 先保存客戶,再保存訂單 5 問題: 6 當我們建立了雙向關聯關系之后,就算是先保存主表,再保存從表,也是會產生5條SQL語句 7 Hibernate: insert into T_CUSTOMERS (NAME, AGE) values (?, ?) 8 Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?) 9 Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?) 10 Hibernate: update T_ORDERS set CUSTOMER_ID=? where id=? 11 Hibernate: update T_ORDERS set CUSTOMER_ID=? where id=? 12 解決辦法: 13 思路:讓主表的集合放棄維護關聯關系的權利。 14 操作方式:注釋上 15 c1.getOrders().add(o1); 16 c1.getOrders().add(o2); 17 */ 18 @Test 19 public void test1(){ 20 //數據准備 21 Customer c1 = new Customer(); 22 c1.setName("testC"); 23 c1.setAge(18); 24 25 Order o1 = new Order(); 26 o1.setOrdernum("C001"); 27 o1.setMoney(100f); 28 29 Order o2 = new Order(); 30 o2.setOrdernum("C002"); 31 o2.setMoney(200f); 32 33 //建立雙向一對多關聯關系 34 o1.setCustomer(c1); 35 o2.setCustomer(c1); 36 37 //c1.getOrders().add(o1); 38 //c1.getOrders().add(o2); 39 40 41 42 43 Session s = HibernateUtil.getSession(); 44 Transaction tx = s.beginTransaction(); 45 //保存操作 46 s.save(c1); 47 s.save(o1); 48 s.save(o2); 49 50 tx.commit(); 51 s.close(); 52 }
b、雙向關聯關系,持久態關聯臨時態的問題及解決
1 /* 2 * 更新操作 3 * 需求: 4 * 先創建一個訂單,然后查詢出來一個客戶。 5 * 建立客戶和新訂單的關聯關系。 6 * 更新客戶 7 * 問題: 8 * 一個持久態對象,關聯了一個臨時態的對象。 9 * 解決辦法: 10 * 配置級聯保存更新 11 * <set name="orders" table="T_ORDERS" cascade="save-update"> 12 */ 13 @Test 14 public void test2(){ 15 16 //創建一個新的訂單 17 Order o1 = new Order();//臨時態 18 o1.setOrdernum("A003"); 19 o1.setMoney(100f); 20 21 Session s = HibernateUtil.getSession(); 22 Transaction tx = s.beginTransaction(); 23 //查詢一個客戶 24 Customer c1 = s.get(Customer.class, 1);//持久態 25 //建立雙向關聯關系 26 c1.getOrders().add(o1); 27 o1.setCustomer(c1); 28 //更新操作 29 s.update(c1); 30 31 tx.commit(); 32 s.close(); 33 }
c、變更關系(關於雙向關聯的處理辦法)
1 /* 2 * 需求:變更關系 3 * 把id為1的訂單,從屬於2號客戶,改為屬於1號客戶 4 * 解決辦法: 5 * 使用配置的方式,來實現讓有集合的一方,放棄維護的權利 6 * inverse:是否放棄維護的權利 7 * 取值:true放棄 和 false 不放棄(默認值)。 8 * inverse用於應該只出現在set元素上 9 * <set name="orders" table="T_ORDERS" cascade="save-update" inverse="true" > 10 */ 11 @Test 12 public void test3(){ 13 Session s = HibernateUtil.getSession(); 14 Transaction tx = s.beginTransaction(); 15 Customer c1 = s.get(Customer.class, 1);//查詢出來1號客戶 16 Order o1 = s.get(Order.class, 1);//查詢出啦1號訂單 17 //建立雙向關聯關系 18 c1.getOrders().add(o1); 19 o1.setCustomer(c1); 20 //更新操作 21 s.update(c1); 22 23 tx.commit(); 24 s.close(); 25 26 //System.out.println(c1.getOrders()); 27 }
為了保持程序的健壯性,建議是雙向關聯,就建立雙向關聯的關系。
弊端:當我們使用了雙向關聯時,會有冗余的SQL語句執行,造成程序的效率下降。
解決辦法:不要雙向維護關聯關系,讓少的一方放棄維護權利。(但不要在代碼中修改,而是寫在配置文件中)
d、解除關系
1 /* 2 * 解除關系 3 * 需求: 4 * 把1訂單和1號客戶之間的關系解除 5 */ 6 @Test 7 public void test6(){ 8 Session s = HibernateUtil.getSession(); 9 Transaction tx = s.beginTransaction(); 10 Customer c1 = s.get(Customer.class,1); 11 Order o1 = s.get(Order.class, 1); 12 13 //解除1號訂單和1號客戶之間的關系 14 c1.getOrders().remove(o1); 15 o1.setCustomer(null); 16 17 18 tx.commit(); 19 s.close(); 20 }
e、刪除操作
/* * 需求: * 刪除一個客戶 * * 在直接刪除客戶的時候,如果客戶的集合屬性(set元素)上沒有配置inverse=true, * 會直接把客戶刪除掉,同時把訂單中關聯改該客戶的id置為null * * 在直接刪除客戶的時候,如果客戶的集合屬性(set元素)上配置了inverse=true, * 如果有訂單引用該客戶的話,則不能刪除成功。 * * 原因: * 因為客戶的集合屬性已經放棄維護和訂單之間的關聯關系,也就是說,它將不能把 * 訂單的CUSTOMER_ID列置為null。 */ @Test public void test5(){ Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); Customer c1 = s.get(Customer.class,3); s.delete(c1); tx.commit(); s.close(); } /* * 需求: * 刪除一個訂單 * * 可以刪除成功 */ @Test public void test4(){ Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); Order o1 = s.get(Order.class, 1); s.delete(o1); tx.commit(); s.close(); } /* * 級聯刪除: * * <set name="orders" table="T_ORDERS" cascade="save-update,delete" inverse="true"> */ @Test public void test8(){ Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); Customer c1 = s.get(Customer.class,2); s.delete(c1); tx.commit(); s.close(); } /* * 孤兒數據 * 在一對多對象關系映射中,數據具有父子關系,當子數據和父數據之間失去了關聯關系。子數據被稱之為孤兒數據 * 孤兒刪除 * 在hibernate中認為孤兒數據,是沒有存在的意義,理應刪除。 * 需要在配置文件中配置 * 孤兒刪除的配置:cascade="delete-orphan" * <set name="orders" table="T_ORDERS" cascade="save-update,delete-orphan" inverse="true"> */ @Test public void test7(){ Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); Customer c1 = s.get(Customer.class,1); Order o1 = s.get(Order.class, 1); //解除1號訂單和1號客戶之間的關系 c1.getOrders().remove(o1); o1.setCustomer(null); //s.update(c1); tx.commit();//有快照機制的存在 s.close(); }
2、映射多對多
2.1、多對多單項映射
a、保存操作
1 /** 2 * 學生的實體模型 3 * @author zhy 4 * 5 * 一個教師可以教授多個學生 6 * 一個學生可以被多個教師教授 7 * 8 * 教師和學生的關系是多對多 9 */ 10 public class Student implements Serializable { 11 12 private Integer id; 13 private String name; 14 private String gender; 15 16 //多對多關系映射 17 private Set<Teacher> teachers = new HashSet<Teacher>(0); 18 19 20 public Integer getId() { 21 return id; 22 } 23 public void setId(Integer id) { 24 this.id = id; 25 } 26 public String getName() { 27 return name; 28 } 29 public void setName(String name) { 30 this.name = name; 31 } 32 public String getGender() { 33 return gender; 34 } 35 public void setGender(String gender) { 36 this.gender = gender; 37 } 38 public Set<Teacher> getTeachers() { 39 return teachers; 40 } 41 public void setTeachers(Set<Teacher> teachers) { 42 this.teachers = teachers; 43 } 44 @Override 45 public String toString() { 46 return "Student [id=" + id + ", name=" + name + ", gender=" + gender + "]"; 47 } 48 }
1 <hibernate-mapping package="cn.itcast.domain"> 2 <class name="Student" table="T_STUDENTS"> 3 <id name="id" column="id"> 4 <generator class="native"></generator> 5 </id> 6 <property name="name" column="NAME"></property> 7 <property name="gender" column="GENDER"></property> 8 <!-- 多對多關系映射 9 set元素: 10 作用:就是用於映射集合屬性 11 屬性: 12 name:指定集合屬性名稱 13 table:指定的是關聯關系表 14 key元素: 15 作用:就是用於指定外鍵的 16 屬性: 17 column:指定當前實體類在關聯關系表中的外鍵 18 many-to-many元素: 19 作用:指定和對方之間的關系是多對多 20 屬性: 21 class:指定對方的實體類名稱 22 column:指定對方在關聯關系表中的外鍵--> 23 <set name="teachers" table="t_teacher_student_ref" cascade="save-update,delete" > 24 <key column="STUDENT_ID"/> 25 <many-to-many class="Teacher" column="TEACHER_ID"/> 26 </set> 27 </class> 28 </hibernate-mapping>
1 /** 2 * 教師的實體模型 3 * @author zhy 4 * 5 * 一個教師可以教授多個學生 6 * 一個學生可以被多個教師教授 7 * 8 * 教師和學生的關系是多對多 9 * 10 */ 11 public class Teacher implements Serializable { 12 13 private Integer id; 14 private String name; 15 private Float salary; 16 17 //多對多關系映射 18 private Set<Student> students = new HashSet<Student>(0); 19 20 public Integer getId() { 21 return id; 22 } 23 public void setId(Integer id) { 24 this.id = id; 25 } 26 public String getName() { 27 return name; 28 } 29 public void setName(String name) { 30 this.name = name; 31 } 32 public Float getSalary() { 33 return salary; 34 } 35 public void setSalary(Float salary) { 36 this.salary = salary; 37 } 38 public Set<Student> getStudents() { 39 return students; 40 } 41 public void setStudents(Set<Student> students) { 42 this.students = students; 43 } 44 @Override 45 public String toString() { 46 return "Teacher [id=" + id + ", name=" + name + ", salary=" + salary + "]"; 47 } 48 }
1 <hibernate-mapping package="cn.itcast.domain"> 2 <class name="Teacher" table="T_TEACHERS"> 3 <id name="id" column="id"> 4 <generator class="native"></generator> 5 </id> 6 <property name="name" column="NAME"></property> 7 <property name="salary" column="SALARY"></property> 8 <!-- 多對多關系映射 --> 9 <set name="students" table="t_teacher_student_ref" cascade="save-update,delete" inverse="true"> 10 <key column="TEACHER_ID"/> 11 <many-to-many class="Student" column="STUDENT_ID"/> 12 </set> 13 </class> 14 </hibernate-mapping>
1 /* 2 * 保存操作 3 * 需求: 4 * 創建:1號學生 2號學生 3號學生 5 * 1號教師 2號教師 6 * 建立關聯關系 7 * 1號教師教過 1號學生和2號學生 8 * 2號教師教過 2號學生和3號學生 9 * 執行保存 10 * 11 * 多對多關系映射,在執行保存操作時,需要有一方放棄維護的權利。 12 * 任意一方 13 */ 14 @Test 15 public void test1(){ 16 //准備數據 17 Teacher t1 = new Teacher(); 18 t1.setName("hqy"); 19 t1.setSalary(500f); 20 21 Teacher t2 = new Teacher(); 22 t2.setName("lx"); 23 t2.setSalary(1000f); 24 25 Student s1 = new Student(); 26 s1.setName("王占青"); 27 s1.setGender("male"); 28 29 Student s2 = new Student(); 30 s2.setName("秦鵬飛"); 31 s2.setGender("male"); 32 33 Student s3 = new Student(); 34 s3.setName("鄭恆明"); 35 s3.setGender("male"); 36 37 //建立關聯關系 38 t1.getStudents().add(s1); 39 t1.getStudents().add(s2); 40 s1.getTeachers().add(t1); 41 s2.getTeachers().add(t1); 42 43 t2.getStudents().add(s2); 44 t2.getStudents().add(s3); 45 s2.getTeachers().add(t2); 46 s3.getTeachers().add(t2); 47 48 Session s = HibernateUtil.getSession(); 49 Transaction tx = s.beginTransaction(); 50 51 //保存操作 52 s.save(t1); 53 54 tx.commit(); 55 s.close(); 56 }
b、刪除操作
未配置級聯:
1 /* 2 * 刪除操作: 3 * 刪除id為1的教師 4 * 注意事項: 5 * 在多對多的刪除操作時,不要配置級聯刪除。 6 */ 7 @Test 8 public void test2(){ 9 Session s = HibernateUtil.getSession(); 10 Transaction tx = s.beginTransaction(); 11 Teacher t1 = s.get(Teacher.class, 1); 12 s.delete(t1); 13 tx.commit(); 14 s.close(); 15 }
如果在Teacher.hbm.xml中配置了級聯,那么刪除時會先刪除teacher1這個老師,然后去刪關聯表中的數據。由於有級聯,還會去學生表中刪除對應的學生數據,但是由於student2這個學生被teacher2老師引用着所以刪不掉。會報錯。
進一步,如果我們配置了雙向級聯,那么結果將是所有數據全沒了。
結論:
1、多對多映射,不要配置級聯刪除。(可以配置級聯保存)
2、雙向多對多映射時,不要雙向維護關系。或讓任意一方放棄權利,注意在配置文件中配置
3、一對一映射
3.1、按照外鍵關聯
3.2、按照主鍵關聯