十、多表映射
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、按照主键关联
时间: 2024-10-14 01:26:18