多对多关联关系的映射
在操作和性能方面都不太理想,所以多对多的映射使用较少,实际使用中最好转换成一对多的对象模型;hibernate会为我们创建中间关联表,转换成两个一对多。
核心配置:
<set name="ss" table="teacher_student">
<key column="teacher_id"></key>
<many-to-many class="Student" column="student_id"></many-to-many>
</set>
Teacher类代码如下:
public class Teacher {
private int id;
private String name;
private Set<Student> ss=null;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Student> getSs() {
return ss;
}
public void setSs(Set<Student> ss) {
this.ss = ss;
}
}
teacher.hbm.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="many2many">
<class name="Teacher" table="teacher">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="ss" table="teacher_student">
<key column="teacher_id"></key>
<many-to-many class="Student" column="student_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
Student类代码如下:
public class Student {
private int id;
private String name;
private Set<Teacher> ts=null;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Teacher> getTs() {
return ts;
}
public void setTs(Set<Teacher> ts) {
this.ts = ts;
}
}
student.hbm.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="many2many">
<class name="Student" table="student">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="ts" table="teacher_student">
<key column="student_id"></key>
<many-to-many class="Teacher" column="teacher_id"></many-to-many>
</set>
//集合标签,通过student_id到关联表teacher_student中查到相应的teacher的id,在利用teacher_id到teacher表中具体的查找teacher信息。
</class>
</hibernate-mapping>
测试代码如下:
public class Main {
public static void main(String[] args) {
add();
}
static void add() {
Set<Teacher> ts = new HashSet<Teacher>();
Set<Student> ss = new HashSet<Student>();
Teacher t1 = new Teacher();
t1.setName("t1");
ts.add(t1);
Teacher t2 = new Teacher();
t2.setName("t2");
ts.add(t2);
Student s1 = new Student();
s1.setName("s1");
ss.add(s1);
Student s2 = new Student();
s2.setName("s2");
ss.add(s2);
t1.setSs(ss);
t2.setSs(ss);
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
s.save(t1);
s.save(t2);
s.save(s1);
s.save(s2);
tx.commit();
}
}
程序执行完毕后,数据库中有三个表:teacher表,student表,关联表teacher_student表。
表结构为:
teacher(id,name)
student(id,name)
teacher_student(teacher_id,student_id)
hibernate会执行四条插入语句,分别把teacher和student信息插入到teacher表和student表中。由于在以上代码中设置了老师知道学生集合,所以还要把教师和学生的多对多关系体现到teacher_student表中去。
还要有四条插入语句。
具体如下:
Hibernate: insert into teacher (name) values (?)
Hibernate: insert into student (name) values (?)
Hibernate: insert into student (name) values (?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
值得注意的是,在以上代码中,只对老师做了setSs操作,而没有对学生做setTs操作,这样没有问题。如果同时做,会重复的将数据插入到关联表中去,这样就会产生12条插入语句,这样会导致插入重复数据错误。所以只需对一方做了设置即可,不需要同时设置。让老师知道学生或者学生知道老师即可,二选其一。
对以上问题还有一种解决方案:就是使用inverse,让老师放弃对关系的维护。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="many2many">
<class name="Teacher" table="teacher">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="ss" table="teacher_student" inverse="true"
>
<key column="teacher_id"></key>
<many-to-many class="Student" column="student_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
测试代码:
static void add() {
Set<Teacher> ts = new HashSet<Teacher>();
Set<Student> ss = new HashSet<Student>();
Teacher t1 = new Teacher();
t1.setName("t1");
ts.add(t1);
Teacher t2 = new Teacher();
t2.setName("t2");
ts.add(t2);
Student s1 = new Student();
s1.setName("s1");
s1.setTs(ts);
ss.add(s1);
Student s2 = new Student();
s2.setName("s2");
s2.setTs(ts);
ss.add(s2);
t1.setSs(ss);
t2.setSs(ss);
Session s = HibernateUtil.getSession();
Transaction tx = s.beginTransaction();
s.save(t1);
s.save(t2);
s.save(s1);
s.save(s2);
tx.commit();
}
以上代码中,对教师和学生做了双向关联,按理会对中间表产生8条插入语句。但是我们在配置文件中,让教师放弃了对关系的维护,所以hibernate就忽略了,从而达到了同样的效果,又可以正确的设定对象模型的关系,又不会产生表主键重复的错误。
设计问题小结:
一般不设计成一对多,而保留多对一。
比如部门和员工的关系。一般在员工类中包含有部门这个属性,其实也可以在部门中拥有一个集合属性,包含它的所有员工,对于数据量不大的情况下可以这样用。但是如果多的一方的数据很多的话,会带来性能问题。向论坛上的一个帖子有若干个回复,采用的是分页查询来回避该问题。所以最好通过员工来导航到部门,而不是通过部门导航到员工,即部门中一般不加上set<Employee>属性。
更不用说,多对多映射涉及到多表查询带来极大的性能开销。要知道多表操作所带来的性能消耗相当客观,所以最好将它转换为其他的方式来处理 。