一.多对多双向关联
以Student和Course为例,一个学生可以选多门课程,一门课程也可以被多个学生选取
持久化类Student
1 持久化类Student.java 2 3 package com.edu.many2many; 4 5 import java.util.HashSet; 6 import java.util.Set; 7 8 public class Student { 9 10 private Integer id; 11 private String name; 12 13 //关联关系 14 Set<Course> courses = new HashSet<Course>(); 15 public Integer getId() { 16 return id; 17 } 18 public void setId(Integer id) { 19 this.id = id; 20 } 21 public String getName() { 22 return name; 23 } 24 public void setName(String name) { 25 this.name = name; 26 } 27 public Set<Course> getCourses() { 28 return courses; 29 } 30 public void setCourses(Set<Course> courses) { 31 this.courses = courses; 32 } 33 34 35 }
持久化类Course
1 持久化类Course.java 2 3 package com.edu.many2many; 4 5 import java.util.HashSet; 6 import java.util.Set; 7 8 public class Course { 9 10 private Integer id; 11 private String name; 12 13 //关联关系 14 Set<Student> students = new HashSet<Student>(); 15 16 public Integer getId() { 17 return id; 18 } 19 public void setId(Integer id) { 20 this.id = id; 21 } 22 public String getName() { 23 return name; 24 } 25 public void setName(String name) { 26 this.name = name; 27 } 28 public Set<Student> getStudents() { 29 return students; 30 } 31 public void setStudents(Set<Student> students) { 32 this.students = students; 33 } 34 35 36 }
Student.hbm.xml对象关系映射文件
1 Student.hbm.xml映射文件 2 3 <?xml version="1.0" encoding="UTF-8"?> 4 <!DOCTYPE hibernate-mapping PUBLIC 5 "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 6 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 7 <hibernate-mapping package="com.edu.many2many"> 8 <class name="Student"> 9 <id name="id"> 10 <generator class="increment"/> 11 </id> 12 13 <property name="name"/> 14 15 <!-- 关联属性 16 cascade属性:级联操作默认值为none 17 save-update:级联保存或更新 18 delete:级联删除 19 all 20 inverse="true":默认值为false。主控方 21 true 反转控制权,变为被控方,没有维护关联关系的权利。 22 --> 23 <!-- courses 和Course类 多对多 24 在多对多关系中,映射两端关系表的名称必须要相同 25 --> 26 <set name="courses" table="stu_cou" > 27 <key column="sid"/> 28 <many-to-many class="Course" column="cid"/> 29 </set> 30 </class> 31 32 33 </hibernate-mapping>
多对多关联关系的实现需要一个连接表,<set>的属性指出的就是连接表的名称,<key>指出连接表参照students表id的外键的字段名;<many-to-many>中的class指定与Student多对多关联的类,column指定连接表参照Course映射表(此处由Course.hbm.xml映射为courses表)id的外键的字段名,Course.hbm.xml中的<set>配置与Student.hbm.xml中<set>相反
Course.hbm.xml对象关系映射文件
1 Course.hbm.xml映射文件 2 3 <?xml version="1.0" encoding="UTF-8"?> 4 <!DOCTYPE hibernate-mapping PUBLIC 5 "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 6 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 7 <hibernate-mapping package="com.edu.many2many"> 8 <class name="Course" table="course"> 9 <id name="id"> 10 <generator class="increment"/> 11 </id> 12 13 <property name="name"/> 14 15 <!-- 映射关联属性 --> 16 <!-- students 和 Student类 多对多 17 name: 关联属性的名字 18 class: 关联类的类名 19 column: 数据库外键字段的名字 20 table:关系表的名称 21 22 多对多关联映射 中cascade="delete" 注意使用:会级联删除对端表的记录 23 --> 24 <set name="students" table="stu_cou" > 25 <key column="cid"/> 26 <many-to-many class="Student" column="sid"/> 27 </set> 28 </class> 29 30 </hibernate-mapping>
两个映射文件中设置的连接表的名称以及连接表中的两个字段名需对应相同,如连接表名都为"stu_cou"两字段为"sid"和"cid",否则会导致不必要的麻烦;连接表的主键为联合主键(sid,cid)
hibernate.cfg.xml映射文件
1 hibernate.cfg.xml映射文件 2 3 <?xml version="1.0" encoding="UTF-8"?> 4 <!DOCTYPE hibernate-configuration PUBLIC 5 "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 6 "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> 7 <hibernate-configuration> 8 <session-factory> 9 <!-- 配置数据库连接的基本信息 --> 10 <property name="hibernate.connection.driver_class"> 11 oracle.jdbc.driver.OracleDriver 12 </property> 13 <property name="hibernate.connection.url"> 14 jdbc:oracle:thin:@localhost:1521:XE 15 </property> 16 <property name="hibernate.connection.username">lihengyu</property> 17 <property name="hibernate.connection.password">lihengyu</property> 18 19 <!-- 数据库的方言 使用的是哪种数据库 --> 20 <property name="hibernate.dialect"> 21 org.hibernate.dialect.Oracle10gDialect 22 </property> 23 <!-- 显示sql语句 --> 24 <property name="hibernate.show_sql">true</property> 25 <!-- 自动创建数据库表 26 none:默认值 有表就使用,没表不创建 27 create:每次都创建新表 28 update: 有表就使用,没表创建。如果持久化类及映射文件发生改变,则发生alter语句修改表结构。 29 --> 30 <property name="hibernate.hbm2ddl.auto">update</property> 31 32 <!-- 添加映射文件 --> 33 <mapping resource="com/edu/many2many/Course.hbm.xml" /> 34 <mapping resource="com/edu/many2many/Student.hbm.xml" /> 35 36 37 </session-factory> 38 39 40 </hibernate-configuration>
TestHibernate.java测试代码
1 TestHibernate .java 2 3 package com.edu.many2many; 4 5 import org.hibernate.SessionFactory; 6 import org.hibernate.Transaction; 7 import org.hibernate.cfg.Configuration; 8 import org.hibernate.classic.Session; 9 import org.junit.Test; 10 11 /** 12 * 使用单元测试工具,测试hibernate 的增删改查(CRUD)操作 13 * 14 * @author Administrator 15 * 16 */ 17 public class TestHibernate { 18 19 private static SessionFactory sf; 20 static { 21 // 1. 加载配置文件 22 Configuration cfg = new Configuration(); 23 cfg.configure("com/edu/many2many/hibernate.cfg.xml"); 24 25 // 2. 获得SessionFactory 26 sf = cfg.buildSessionFactory(); 27 } 28 29 30 /** 31 * 保存用户 32 * 33 */ 34 @Test 35 public void save() { 36 37 38 Session session = sf.openSession(); 39 Transaction tran = session.beginTransaction(); 40 41 Course c1 = new Course(); 42 c1.setName("体育"); 43 Course c2 = new Course(); 44 c2.setName("音乐"); 45 46 Student s1 = new Student(); 47 s1.setName("张三"); 48 Student s2 = new Student(); 49 s2.setName("李四"); 50 51 //维护关联关系 52 c1.getStudents().add(s1); 53 c1.getStudents().add(s2); 54 55 c2.getStudents().add(s1); 56 c2.getStudents().add(s2); 57 /** 58 * 如果程序中,需要从关联两端同时维护关系,则其中一端必须交出控制权 59 * 变为被控方,被控方不在维护关联关系 60 */ 61 s1.getCourses().add(c1); 62 s1.getCourses().add(c2); 63 64 s2.getCourses().add(c1); 65 s2.getCourses().add(c2); 66 67 session.save(c1); 68 session.save(c2); 69 session.save(s1); 70 session.save(s2); 71 72 tran.commit(); 73 session.close(); 74 } 75 76 /** 77 * 查询 78 * 79 */ 80 @Test 81 public void find() { 82 83 84 Session session = sf.openSession(); 85 Transaction tran = session.beginTransaction(); 86 87 //谁选择了id=1的课程 88 Course c1 = (Course) session.get(Course.class, 1); 89 for (Student stu : c1.getStudents()) { 90 System.out.println(stu.getName()); 91 } 92 93 tran.commit(); 94 session.close(); 95 } 96 97 //删除1号课程 98 @Test 99 public void delete() { 100 101 102 Session session = sf.openSession(); 103 Transaction tran = session.beginTransaction(); 104 105 106 Course c1 = (Course) session.get(Course.class, 1); 107 108 session.delete(c1); 109 110 //如果student是被控方,不能级联删除外表记录 111 /*Student s1 = (Student) session.get(Student.class, 1); 112 session.delete(s1);*/ 113 114 tran.commit(); 115 session.close(); 116 } 117 }
(1)如果两个映射文件的inverse都设为false(默认),则会出现异常(主键重复)导致插入失败:
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
Caused by: Java.sql.BatchUpdateException: Duplicate entry ‘1-1‘ for key ‘PRIMARY‘
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:Duplicate entry ‘1-1‘ for key ‘PRIMARY‘
解释:应为两映射文件中的inverse都为true,则Student和Course都去维护关联关系,即同时向连接表中插入记录,则会导致主键重复而插入失败。
解决办法:
——将其中一方的inverse设为true,让对方维持关联关系;
——将s1.getCourses().add(c1);或 c1.getStudents().add(s1);删除,因为若某个Course中的students集合为空时,它就不会去向连接表中添加记录,也就不会与Student向连接表中插入记录时冲突而主键重复。
(2)如果都设为true,则都不会向连接表中插入记录而只是向两表中插入记录(两者都认为对方会维持关联关系)
(3)如果不是Student维持关联关系:
——若连接表stu_cou中有参照students表中该记录的记录(即在stu_cou表中存在sid为2的记录)时,则删除失败。
——若连接表stu_cou中没有参照students表中该记录的记录时,则可以成功地将该记录删除。
(4)如果是Student维持关联关系:
——先将连接表stu_cou中参照students表中该记录的记录删除,然后将该学生记录从students表中删除。