多对多(many-to-many):在操作和性能方面都不太理想,所以多对多的映射使用较少,实际使用中最好转换成一对多的对象模型;hibernate会为我们创建中间关联表,转换成两个一对多。 1. E-R图
2. 实体类: Teacher实体类如下:
package com.reiyen.hibernate.domain; import java.util.Set; public class Teacher { private int id; private String name; private Set<Student> students; //setter和getter方法 } Student实体类如下:
package com.reiyen.hibernate.domain; import java.util.Set; public class Student { private int id; private String name; private Set<Teacher> teachers; //setter和getter方法 } 3.映射文件如下: Teacher.hbm.xml如下:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.reiyen.hibernate.domain"> <class name="Teacher"> <id name="id"> <generator class="native" /> </id> <property name="name" /> <!-- 通过table项告诉hibernate中间表的名称 --> <set name="students" table="teacher_student"> <!-- 通过key属性告诉hibernate在中间表里面查询teacher_id值相应的teacher记录 --> <key column="teacher_id" /> <!-- 通过column项告诉hibernate对student表中查找student_id值相就的studnet记录 --> <many-to-many class="Student" column="student_id" /> </set> </class> </hibernate-mapping>
Student.hbm.xml如下:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.reiyen.hibernate.domain"> <class name="Student" > <id name="id" > <generator class="native" /> </id> <property name="name" /> <set name="teachers" table="teacher_student"> <key column="student_id" /> <many-to-many class="Teacher" column="teacher_id"/> </set> </class> </hibernate-mapping> 一定要注意映射文件中<many-to-many class="Teacher" column="teacher_id"/>中class的值,它必须与你另一个关联映射文件中的class属性的name值一致,其实就是与你的实体类的类名一致,如:<many-to-many class="Teacher" column="teacher_id"/>中class的值就不能写成"teacher"。如果写成这样的话,就会抛出如下异常:An association from the table teacher_student refers to an unmapped class: com.reiyen .hibernate.domain.teacher 4. 测试程序如下:
public class Many2Many { public static void main(String[] args) { add(); //query(1); } static void query(int id) { Session s = null; Transaction tx = null; try { s = HibernateUtil.getSession(); tx = s.beginTransaction(); Teacher t = (Teacher) s.get(Teacher.class, id); System.out.println("students:" + t.getStudents().size()); tx.commit(); } finally { if (s != null) s.close(); } } static void add() { Session s = null; Transaction tx = null; try { Set<Teacher> ts = new HashSet<Teacher>(); Teacher t1 = new Teacher(); t1.setName("t1 name"); ts.add(t1); Teacher t2 = new Teacher(); t2.setName("t2 name"); ts.add(t2); Set<Student> ss = new HashSet<Student>(); Student s1 = new Student(); s1.setName("s1"); ss.add(s1); Student s2 = new Student(); s2.setName("s2"); ss.add(s2); t1.setStudents(ss); //1 t2.setStudents(ss); //1 // // s1.setTeachers(ts); //2 // s2.setTeachers(ts); //2 s = HibernateUtil.getSession(); tx = s.beginTransaction(); s.save(t1); s.save(t2); s.save(s1); s.save(s2); tx.commit(); } finally { if (s != null) s.close(); } } } 运行此程序后:控制台打印的sql语句如下所示: Hibernate: insert into Teacher (name) values (?) 一共在中间表里面插入了4条记录。 中间表结构如下所示: DROP TABLE IF EXISTS `test`.`teacher_student`; 表中插入的记录如下所示: mysql> select * from teacher_student;
程序中注释为1的语句非常重要,它是建立Teacher与Student关联的语句,如果没有这两条语句,虽然程序照样会执行,但是在中间表teacher_student没有任何记录,也就是Teacher与Student之间未关联。 当然你也可以通过程序中注释为2的语句来建立Teacher与Student之间的关联关系,同样会产生与注释为1的语句的效果。但是你不能在程序中同时出现以上四句程序,否则会抛出异常( PRIMARY KEY (`student_id`,`teacher_id`),所以会出现主键冲突的异常 ),: Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?) 解决上面产生异常的办法是设置inverse属性。即在Tearcher一端或Student一端设置inverse="true",即让他们之中的某一方放弃维护关联关系。此时,虽然上面四句程序在测试程序中同时出现了(其实就是在对象模型上相互设置了他们的关联关系),但程序照样能运行正常,因为了在数据库模型上,只会有两句程序生效,也就是没有设置inverse="true"的那一端会去维护关联关系。有关inverse的说细信息,可以参看我的文章hibernate级联(cascade和inverse).
执行测试程序中的查询测试,控制台打印的信息如下所示: Hibernate: select teacher0_.id as id5_0_, teacher0_.name as name5_0_ from Teacher teacher0_ where teacher0_.id=? 从打印出的sql语句可以看出,多对多关系进行查询时,效率是比较低的。 |