Hibernate的inverse和cascade都含有维护相互关系的意思。根据网上的解释cascade表示级联关系,而inverse表示由谁来维护关系。只从字面上不是很好理解,所以下面结合实例来说明下inverse和cascade的区别,简单起见只用双向多对一来说明,下面是本例用到的两张表。
School表结构:
对应的实体类:
package com.jaeger.hibernate.test; import java.util.Set; publicclass School { private int schoolId; private String schoolName; private Set<Student> schoolStudents; publicint getSchoolId() { return schoolId; } publicvoid setSchoolId(intschoolId) { this.schoolId = schoolId; } public String getSchoolName() { return schoolName; } publicvoid setSchoolName(String schoolName) { this.schoolName = schoolName; } public Set<Student>getSchoolStudents() { return schoolStudents; } publicvoid setSchoolStudents(Set<Student> schoolStudents) { this.schoolStudents = schoolStudents; } }
School.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/HibernateMapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.jaeger.hibernate.test.School" table="school"> <id name="schoolId" column="school_id"> <generator class="native"></generator> </id> <property name="schoolName" column="school_name" /> <set name="schoolStudents"> <key column="student_school_id"></key> <one-to-many class="com.jaeger.hibernate.test.Student"/> </set> </class> </hibernate-mapping>
Student表结构(student_school_id为外键):
对应的实体类:
package com.jaeger.hibernate.test; publicclass Student { private int studentId; private String studentName; private School studentSchool; publicint getStudentId() { return studentId; } publicvoid setStudentId(intstudentId) { this.studentId = studentId; } public String getStudentName() { return studentName; } publicvoid setStudentName(String studentName) { this.studentName = studentName; } public School getStudentSchool() { return studentSchool; } publicvoid setStudentSchool(School studentSchool) { this.studentSchool = studentSchool; } }
Student.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/HibernateMapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.jaeger.hibernate.test.Student" table="student"> <id name="studentId" column="student_id"> <generator class="native"></generator> </id> <property name="studentName" column="student_name" /> <many-to-one name="studentSchool" class="com.jaeger.hibernate.test.School" foreign-key="fk_student_school" column="student_school_id"></many-to-one> </class> </hibernate-mapping>
1. Cascade
Cascade有很多取值,此处使用最常用的save-update进行说明。
① 没有配置cascade的情况
在Student.hbm.xml和School.hbm.xml里面我们都没有配置cascade。
@Test public void createDataFromManySide(){ Configuration config = new Configuration() .configure("com/jaeger/hibernate/test/hibernate.cfg.xml"); ServiceRegistry sr = newStandardServiceRegistryBuilder() .applySettings(config.getProperties()).build(); SessionFactory sf = config.buildSessionFactory(sr); Session session = sf.openSession(); Transaction transaction = session.beginTransaction(); School school1 = new School(); school1.setSchoolName("KONAMI"); Student student1 = new Student(); student1.setStudentName("小岛秀夫"); student1.setStudentSchool(school1); School school2 = new School(); school2.setSchoolName("NINTENDO"); Student student2 = new Student(); student2.setStudentName("岩田聪"); student2.setStudentSchool(school2); Student student3 = new Student(); student3.setStudentName("宫本茂"); student3.setStudentSchool(school2); session.save(student1); //⑴ session.save(student2); //⑵ session.save(student3); //⑶ transaction.commit(); session.close(); }
上面例子中student1、student2、student3里面含有school1和school2,但我们如果只save 3个
student的时候,hibernate会报错,告诉我们必须先保存school1和school2。
org.hibernate.TransientObjectException:object references an unsaved transient instance
- save the transient instancebefore flushing: com.jaeger.hibernate.test.School
② 把cascade设置为save-update
修改Student.hbm.xml里面的配置为:
<many-to-one name="studentSchool" class="com.jaeger.hibernate.test.School" foreign-key="fk_student_school"column="student_school_id" cascade="save-update"> </many-to-one>
这个时候我们再去save 3个student就没有任何问题了,结果如下:
School表:
Student表:
同样,我们也可以吧student1、student2、student3存入school1和school2,最后保存school1和
school2。只要把School.hbm.xml里面的配置改为:
<set name="schoolStudents" cascade="save-update"> <key column="student_school_id"></key> <one-to-many class="com.jaeger.hibernate.test.Student" /> </set>
测试方法如下:
@Test publicvoid createDataFromOneSide(){ Configuration config = new Configuration() .configure("com/jaeger/hibernate/test/hibernate.cfg.xml"); ServiceRegistry sr = newStandardServiceRegistryBuilder() .applySettings(config.getProperties()).build(); SessionFactory sf = config.buildSessionFactory(sr); Session session = sf.openSession(); Transaction transaction = session.beginTransaction(); Student student1 = new Student(); student1.setStudentName("小岛秀夫"); Set<Student> students1 = newHashSet<Student>(); students1.add(student1); School school1 = new School(); school1.setSchoolName("KONAMI"); school1.setSchoolStudents(students1); Student student2 = new Student(); student2.setStudentName("岩田聪"); Student student3 = new Student(); student3.setStudentName("宫本茂"); Set<Student> students2 = newHashSet<Student>(); students2.add(student2); students2.add(student3); School school2 = new School(); school2.setSchoolName("NINTENDO"); school2.setSchoolStudents(students2); session.save(school1); session.save(school2); transaction.commit(); session.close(); }
2. Inverse
inverse用来指明由哪方来维护引用关系,如果inverse="true"表示让出维护关系的权力,让对方来
维护关系,而inverse="false"表示本方也需要维护关系。<many-to-one></many-to-one>标签不可以
设置invserse属性,它默认自带inverse="false"。
inverse一般用于<one-to-many>标签,所以下面就用<one-to-many>标签来说明。把上面
School.hbm.xml的配置改为:
<set name="schoolStudents" cascade="save-update" inverse="true"> <key column="student_school_id"></key> <one-to-many class="com.jaeger.hibernate.test.Student" /> </set>
表示school类不再维护与student之间的关系,这样说比较抽象。我们再次运行上面
createDataFromOneSide()测试方法,运行结果如下:
School表:
Student表:
此时Student表的外键为空,因为School类已经声明为不维护与Student的关系,虽然Student里面创
建了记录,但不会保存指向School的外键。
3. <one-to-many>中inverse和cascade其他组合的效果
① cascade="save-update" inverse="false"
<one-to-many>中如果不配置inverse,则默认为inverse="false"。这种配置可以从通过School来创
建Student,并添加Student的外键。
② 只配inverse="false"
这种配置会导致save出错,必须要手动先save Student,因为不允许save School的时候创建
Student。
③ 只配inverse="true"
运行结果如下:
School表:
Student表:
此时只保存了School的数据,因为虽然不允许save School的时候创建Student,但School也不需要
维护与Student的关系,所以只保存School的数据即可。
4. 从one维护关系和many端维护关系的不同
我们从Hibernate生成的sql来看看,两种关系维护方法的不同。我们还是用上面的Student和School
的例子来说明。
① 把School放入Student,然后save Student,生成的sql如下:
Hibernate: insert into school (school_name) values (?) Hibernate: insert into student (student_name, student_school_id) values (?, ?)
② 把Student放入School,然后save School,生成的sql如下:
Hibernate: insert into school (school_name) values (?) Hibernate: insert into student (student_name, student_school_id) values (?, ?) Hibernate: update student set student_school_id=? where student_id=?
可以看到从many端来维护关系的话,Hibernate会先插入School和Student数据,但Student里面的外
键为null,最后在update Student的外键。相对于从one端维护many端来维护的话会多一个update语
句,如果数据量很大,会多出很多不必要的update操作。
而且这种方法要求Student表的外键不能是not-null的,否则开始的insert会出错。