首页 > 代码库 > 一对多(多对一)关系中的inverse和cascade属性
一对多(多对一)关系中的inverse和cascade属性
首先说一下inverse:
"inverse" 直译过来就是"反转,使颠倒"的意思,书面化的解释为"是否将关系维护的权力交给对方"
1. 在hibernate中inverse默认是false,也就是己方拥有维护关系的权利, 当然为true的话,就是把维护关系的权利交给了对方
2. 在一对多的关系(多对一)中,通常将一端的inverse设置为false(一端设为true的话会多出更新语句,有性能问题,下面会讲到),而多对多的关系中,inverse的值只能有一个为true,因为如果双发都为true,那么双方都去维护关系,会造成中间关系表中出现重复的数据。(这一点以后有深刻的理解,会补上解释的额)
说到这里,那么问题来了,什么是关系?关系的具体体现又是什么?
什么是关系?
"关系"就是两个表之间的关系,通常为"一对多","一对一","多对多"三种关系,
关系的具体体现是什么?
暂且先看下面这张图,后面会详细讲解
这是AClazz.hbm.xml中的部分截图,在一端配置外键关系时,关系的具体体现就是column="clazzid",将来这一列会在学生表中生成(外键)
接下来谈一谈cascade:
1. "cascade"-直译过来就是"级联、串联"的意思,书面化的解释为"该属性会使我们在操作主对象时,同时Hibernate帮助我们完成从属对象 相应的操作
(比如,有Customer和Order这两张表,关系为一对多,只使用JDBC删除Customer表中的一行记录时,我们还需要手动的将 Order表中与之关联的记录全都删除,使用Hibernate的‘cascade‘属性后,当我们删除一条Customer记录时,Hibernate 会帮助我们完成相应Order表记录的删除工作,方便了我们的工作)"。
2. 用"cascade"属性时,主对象(一 方)一般设置为"all",而多方不建议设置包含delete操作的选项,建议设置多方为"save-update",这是因为你删除一方,多方已经没有 存在的意义了,而删除多方不能代表一方没意义了(例如,教室和学生)
举个例子
比如: AClazz.hbm.xml -->一端
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 3 <!-- 4 hbm.xml的作用 5 将pojo中每一个属性对应到表的列名 6 --> 7 <hibernate-mapping> 8 <class name="com.bjsxt.hibernate.pojo.AClazz" table="a_clazz"> 9 <!-- id必须写name --> 10 <id name="id"> 11 <generator class="native"/> 12 </id> 13 <property name="name"/> 14 <property name="createTime"/> 15 <!-- 一对多如何设置 --> 16 <set name="studentSet" inverse="false" cascade="all" fetch="subselect"> 17 <!-- 配置外键,两张表的关联关系 --> 18 <key column="clazzid"></key> 19 20 <!-- 另一端对应的类 --> 21 <one-to-many class="com.bjsxt.hibernate.pojo.AStudent"/> 22 </set> 23 </class> 24 </hibernate-mapping>
AStudent.hbm.xml -->多端
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 3 <!-- 4 hbm.xml的作用 5 将pojo中每一个属性对应到表的列名 6 --> 7 <hibernate-mapping> 8 <class name="com.bjsxt.hibernate.pojo.AStudent" table="a_student"> 9 <!-- id必须写name --> 10 <id name="id"> 11 <generator class="native"/> 12 </id> 13 <property name="name"/> 14 <property name="createTime"/> 15 <many-to-one name="clazz" column="clazzid" class="com.bjsxt.hibernate.pojo.AClazz" cascade="save-update"></many-to-one> 16 </class> 17 </hibernate-mapping>
运用
了解了inverse,cascade 下面一对多(双向)综合运用一下
1.下面这张截图时eclipse中的项目目录
2.根据上面的目录结构首先创建pojo类
2.1 AClazz.java -- 一端
2.1 AStudent.java -- 多端
3.配置映射文件
3.1 AClazz.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- hbm.xml的作用 将pojo中每一个属性对应到表的列名 --> <hibernate-mapping> <class name="com.bjsxt.hibernate.pojo.AClazz" table="a_clazz"> <!-- id必须写name --> <id name="id"> <generator class="native"/> </id> <property name="name"/> <property name="createTime"/> <!-- 一对多如何设置 --> <set name="studentSet" inverse="false" cascade="all"> <!-- 配置外键,两张表的关联关系 --> <key column="clazzid"></key> <!-- 另一端对应的类 --> <one-to-many class="com.bjsxt.hibernate.pojo.AStudent"/> </set> </class> </hibernate-mapping>
以上的<set>....</set>它就相当于一个纽带,好比一个人牵了多只牛
3.2 AStudent.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- hbm.xml的作用 将pojo中每一个属性对应到表的列名 --> <hibernate-mapping> <class name="com.bjsxt.hibernate.pojo.AStudent" table="a_student"> <!-- id必须写name --> <id name="id"> <generator class="native"/> </id> <property name="name"/> <property name="createTime"/> <many-to-one name="clazz" column="clazzid" class="com.bjsxt.hibernate.pojo.AClazz" cascade="save-update"></many-to-one> </class> </hibernate-mapping>
别忘了在hibernate.cfg.xml中添加上面的两个映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <!-- session-factory == DriverManager session == Connection --> <hibernate-configuration> <session-factory> <!-- 驱动包名 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <!-- JDBC URL --> <property name="hibernate.connection.url">jdbc:mysql://127.0.0.1:3306/hibernate</property> <!-- JDBC 用户名 --> <property name="hibernate.connection.username">root</property> <!-- JDBC 密码 --> <property name="hibernate.connection.password">admin</property> <!-- 官方语言,告诉hibernate连接的是哪个数据库 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <!-- 是否显示SQL语句 --> <property name="hibernate.show_sql">true</property> <!-- 格式化SQL语句 --> <property name="hibernate.format_sql">true</property> <!-- create:每次执行,都创建一张新表,将原来的记录删除 update:每次执行,如果映射文件有修改,仅仅修改表结构 --> <property name="hibernate.hbm2ddl.auto">update</property> <!-- 告诉hibernate核心配置文件,加载哪些映射文件 --> <mapping resource="com/bjsxt/hibernate/pojo/AStudent.hbm.xml"/> <mapping resource="com/bjsxt/hibernate/pojo/AClazz.hbm.xml"/> </session-factory> </hibernate-configuration>
注意测试一以后的所有测试,都是基于测试一上所做的修改
4.JUnit测试
保持 AClazz.hbm.xml 和 AStudent.hbm.xml 中级联关系不变,只修改AClazz.hbm.xml 中的inverse属性
创建一个班级,多个学生,因为配置了级联关系,所以我把session.save(student)注释了,这样只需要保存班级即可
@Test public void inserClazz() { Session session = null; Transaction transaction = null; try { // 获取Session==Connection session = sessionFactory.openSession(); // 手动开启事务 transaction = session.beginTransaction(); // 创建班级 AClazz clazz = new AClazz(); clazz.setName("405"); clazz.setCreateTime(new Date()); Set<AStudent> studentSet = new HashSet<>(); // 一个班级有多个学生 for (int i = 0; i < 3; i++) { AStudent student = new AStudent(); student.setName("张三_" + i); student.setCreateTime(new Date()); studentSet.add(student); // session.save(student); } // 将学生集合放到班级中 clazz.setStudentSet(studentSet); session.save(clazz); // 手动提交事务 transaction.commit(); } catch (Exception e) { e.printStackTrace(); // 事务回滚 transaction.rollback(); } finally { if (session != null && session.isOpen()) { session.close(); } } }
现在保持 AClazz.hbm.xml 和 AStudent.hbm.xml 中级联关系不变,只修改AClazz.hbm.xml 中的inverse属性
测试一:当AClazz.hbm.xml 中inverse属性为false时
运行inserClazz() ,控制台语句输出结果如下:
mysql数据库截图如下:
a_clazz
a_student
解析:我们可以看到,先是创建了两个表,教室表和学生表,因为我配置了一对多的关系one-to-many(我这里是双向配置,即一对多双向配置,只配一端的也可以),所以会为a_stuent表设置外键,
因为我再AClazz.hbm.xml中配置了级联all,所以我再保存班级的时候会自动将学生保存到数据库中。
又因为我将AClazz.hbm.xml中的inverse属性设置成了false,代表着教师表可以维护它与学生表之间的关系,也就是在保存班级的时候,可以将学生表中的外键clazzid设置上,所以最后也就出现了三条update语句,为a_student的外键更新值,正是因为在一端将inverse设置为false(inverse不设置的话,默认值false),所以再保存完学生之后,一端还要为多端设置外键,现在是插入了三个学生,如果我要是插入了10000个学生呢?There is no doubt that 最后会多出10000条更新语句,所以说我们一般将一端的inverse设置为false,将维护关系的权利交给多方,然后配置级联(一端为all,多端为save-update),这样的话,我们只需要保存多端的对象,即学生,那么最后就不会生成update语句了。(测试四验证这句话)
当然如果我把AClazz.hbm.xml中的inverse属性设置成true,代表着教室表失去了维护关系的权利,也就是说在保存班级的时候,不可以将学生表中的外键clazzid设置上,所以最后自然也就不会出现三条update语句,那么a_student中外键那一列也就变成了null
下面验证我刚才的说法:
测试二:当AClazz.hbm.xml 中inverse属性为true时
运行inserClazz() ,控制台语句输出结果如下:
mysql数据库截图如下:
a_clazz
a_student
看见了吧,以上的结果证实了我刚才的说法是正确的
测试三:现在将AClazz.hbm.xml 中的级联关系删除,并且中inverse属性为true,其他保持不变
运行inserClazz() ,控制台语句输出结果如下:
可以看到hibernate表帮我们创建了,外键也帮我们加上了,但是却报出了异常,
报的是一个临时对象异常:对象的引用是一个为保存的持久化实例,说白了就是没有设置级联
但是hibernate帮我们把表建好了,只不过是空的
a_clazz
a_student
测试四:现在将AClazz.hbm.xml 中的级联关系依旧设置为all,并且inverse属性为true,
单元测试更改如下:(注意我把set集合放学生,以及最后的保存班级都注释了,下面的测试代码中只保存了学生 )
/** * 保存方法 */ @Test public void inserClazz() { Session session = null; Transaction transaction = null; try { // 获取Session==Connection session = sessionFactory.openSession(); // 手动开启事务 transaction = session.beginTransaction(); // 创建班级 AClazz clazz = new AClazz(); clazz.setName("405"); clazz.setCreateTime(new Date()); Set<AStudent> studentSet = new HashSet<>(); // 一个班级有多个学生 for (int i = 0; i < 3; i++) { AStudent student = new AStudent(); student.setName("张三_" + i); student.setCreateTime(new Date()); student.setClazz(clazz); //studentSet.add(student); session.save(student);//只保存学生,因为多端配置了级联关系,save-update,所以保存学生的时候先保存班级 } /*// 将学生集合放到班级中 clazz.setStudentSet(studentSet); session.save(clazz);*/ // 手动提交事务 transaction.commit(); } catch (Exception e) { e.printStackTrace(); // 事务回滚 transaction.rollback(); } finally { if (session != null && session.isOpen()) { session.close(); } } }
运行inserClazz() ,控制台语句输出结果如下:
mysql数据库截图如下:
a_clazz
a_student
解析:从测试四的运行结果,我们可以清楚的看到,在保存学生的时候(多端),因为学生映射xml中配置了级联,所以先保存班级(一端),这样保存班级的时候返回该班级的主键,然后保存学生的时候就有了外键,所以最后并没有三条更新语句,从而提高了性能
参考:http://www.cnblogs.com/o-andy-o/archive/2012/03/26/2418235.html
一对多(多对一)关系中的inverse和cascade属性