1. 程式人生 > 實用技巧 >hibernate多對多(many-to-many)

hibernate多對多(many-to-many)

hibernate多對多(many-to-many):在操作和效能方面都不太理想,所以多對多的對映使用較少,實際使用中最好轉換成一對多的物件模型;hibernate會為我們建立中間關聯表,轉換成兩個一對多。

1. E-R圖


2. 實體類:

Teacher實體類如下:

Java程式碼
  1. packagecom.reiyen.hibernate.domain;
  2. importjava.util.Set;
  3. publicclassTeacher{
  4. privateintid;
  5. privateStringname;
  6. privateSet<Student>students;
  7. //setter和getter方法
  8. }
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實體類如下:

Java程式碼
  1. packagecom.reiyen.hibernate.domain;
  2. importjava.util.Set;
  3. publicclassStudent{
  4. privateintid;
  5. privateStringname;
  6. privateSet<Teacher>teachers;
  7. //setter和getter方法
  8. }
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程式碼
  1. <?xmlversion="1.0"?>
  2. <!DOCTYPEhibernate-mappingPUBLIC
  3. "-//Hibernate/HibernateMappingDTD3.0//EN"
  4. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  5. <hibernate-mappingpackage="com.reiyen.hibernate.domain">
  6. <classname="Teacher">
  7. <idname="id">
  8. <generatorclass="native"/>
  9. </id>
  10. <propertyname="name"/>
  11. <!--通過table項告訴hibernate中間表的名稱-->
  12. <setname="students"table="teacher_student">
  13. <!--通過key屬性告訴hibernate在中間表裡面查詢teacher_id值相應的teacher記錄-->
  14. <keycolumn="teacher_id"/>
  15. <!--通過column項告訴hibernate對student表中查詢student_id值相就的studnet記錄-->
  16. <many-to-manyclass="Student"column="student_id"/>
  17. </set>
  18. </class>
  19. </hibernate-mapping>
<?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程式碼
  1. <?xmlversion="1.0"?>
  2. <!DOCTYPEhibernate-mappingPUBLIC
  3. "-//Hibernate/HibernateMappingDTD3.0//EN"
  4. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  5. <hibernate-mappingpackage="com.reiyen.hibernate.domain">
  6. <classname="Student">
  7. <idname="id">
  8. <generatorclass="native"/>
  9. </id>
  10. <propertyname="name"/>
  11. <setname="teachers"table="teacher_student">
  12. <keycolumn="student_id"/>
  13. <many-to-manyclass="Teacher"column="teacher_id"/>
  14. </set>
  15. </class>
  16. </hibernate-mapping>
<?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. 測試程式如下:

Java程式碼
  1. publicclassMany2Many{
  2. publicstaticvoidmain(String[]args){
  3. add();
  4. //query(1);
  5. }
  6. staticvoidquery(intid){
  7. Sessions=null;
  8. Transactiontx=null;
  9. try{
  10. s=HibernateUtil.getSession();
  11. tx=s.beginTransaction();
  12. Teachert=(Teacher)s.get(Teacher.class,id);
  13. System.out.println("students:"+t.getStudents().size());
  14. tx.commit();
  15. }finally{
  16. if(s!=null)
  17. s.close();
  18. }
  19. }
  20. staticvoidadd(){
  21. Sessions=null;
  22. Transactiontx=null;
  23. try{
  24. Set<Teacher>ts=newHashSet<Teacher>();
  25. Teachert1=newTeacher();
  26. t1.setName("t1name");
  27. ts.add(t1);
  28. Teachert2=newTeacher();
  29. t2.setName("t2name");
  30. ts.add(t2);
  31. Set<Student>ss=newHashSet<Student>();
  32. Students1=newStudent();
  33. s1.setName("s1");
  34. ss.add(s1);
  35. Students2=newStudent();
  36. s2.setName("s2");
  37. ss.add(s2);
  38. t1.setStudents(ss);//1
  39. t2.setStudents(ss);//1
  40. //
  41. //s1.setTeachers(ts);//2
  42. //s2.setTeachers(ts);//2
  43. s=HibernateUtil.getSession();
  44. tx=s.beginTransaction();
  45. s.save(t1);
  46. s.save(t2);
  47. s.save(s1);
  48. s.save(s2);
  49. tx.commit();
  50. }finally{
  51. if(s!=null)
  52. s.close();
  53. }
  54. }
  55. }
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 (?)
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 (?, ?)

一共在中間表裡面插入了4條記錄。

中間表結構如下所示:

DROP TABLE IF EXISTS `test`.`teacher_student`;
CREATE TABLE `test`.`teacher_student` (
`teacher_id` int(11) NOT NULL,
`student_id` int(11) NOT NULL,
PRIMARY KEY (`student_id`,`teacher_id`),
KEY `FK2E2EF2DE6C8A2663` (`teacher_id`),
KEY `FK2E2EF2DE5BEEDBC3` (`student_id`),
CONSTRAINT `FK2E2EF2DE5BEEDBC3` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`),
CONSTRAINT `FK2E2EF2DE6C8A2663` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表中插入的記錄如下所示:

mysql> select * from teacher_student;
+------------+------------+
| teacher_id | student_id |
+------------+------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
+------------+------------+
4 rows in set (0.00 sec)

程式中註釋為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 (?, ?)
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 (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Exception in thread "main" org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update

解決上面產生異常的辦法是設定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=?
Hibernate: select students0_.teacher_id as teacher1_1_, students0_.student_id as student2_1_, student1_.id as id7_0_, student1_.name as name7_0_ from teacher_student students0_ left outer join Student student1_ on students0_.student_id=student1_.id where students0_.teacher_id=?
students:2

從打印出的sql語句可以看出,多對多關係進行查詢時,效率是比較低的。

本文來自轉載