Hibernate中的inverse和cascade,这两个属性都用于一多对(one-to-many)或者多对多(many-to-many)的关系中。
概括的来说,inverse代表是否由己方维护关系,cascade代表是否执行级联操作。接下来,举一列子来更加详细的说明这一关系。
假设有T_Department(部门表)和T_Employee(员工表),它们存在一对多的关系。表的定义如下:
create table T_Department( id int auto_increment, name varchar(20), primary key(id) -- 主键 ) create table T_Employee( id int auto_increment, name varchar(20), salary int, deptId int, primary key(id), -- 主键 key fk_emp_dept (deptId),-- 外键 constraint cons_emp_dept foreign key(deptId) references T_Department(id) on update cascade on delete cascade -- 外键约束 )
一个部门里面,可以有多个员工,也就是1:N的关系。这个1:N的关系是通过T_Department的主键id和T_Employee的外键deptId关联实现的。
(1)Hibernate是一个ORM框架,实现了Java对象与数据表之间的映射。上面谈到的T_Department和T_Employee是数据库中的表,在Hibernate中也存在相应的JavaBean类与两个表相对应,即Department类和Employee类。
Department.java
package com.rk.hibernate.g_one2Many; import java.util.Set; public class Department { private int deptId; private String deptName; private Set<Employee> emps; public int getDeptId() { return deptId; } public void setDeptId(int deptId) { this.deptId = deptId; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public Set<Employee> getEmps() { return emps; } public void setEmps(Set<Employee> emps) { this.emps = emps; } @Override public String toString() { return "Department [deptId=" + deptId + ", deptName=" + deptName + "]"; } }
Department.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.rk.hibernate.g_one2Many" auto-import="true"> <class name="Department" table="T_Department"> <id name="deptId" column="id"> <generator class="native"></generator> </id> <property name="deptName" column="name" type="string"></property> <set name="emps" table="T_Employee" inverse="true" cascade="save-update,delete"> <key column="deptId"></key> <one-to-many class="Employee"/> </set> </class> </hibernate-mapping>
Employee.java
package com.rk.hibernate.g_one2Many; public class Employee { private int empId; private String empName; private double salary; private Department dept; public int getEmpId() { return empId; } public void setEmpId(int empId) { this.empId = empId; } public String getEmpName() { return empName; } public void setEmpName(String empName) { this.empName = empName; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public Department getDept() { return dept; } public void setDept(Department dept) { this.dept = dept; } @Override public String toString() { return "Employee [empId=" + empId + ", empName=" + empName + ", salary=" + salary + "]"; } }
Employee.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.rk.hibernate.g_one2Many" auto-import="true"> <class name="Employee" table="T_Employee"> <id name="empId" column="id"> <generator class="native"></generator> </id> <property name="empName" column="name"></property> <property name="salary" column="salary"></property> <many-to-one name="dept" column="deptId" class="Department"></many-to-one> </class> </hibernate-mapping>
(2)Hibernate是一个ORM框架,实现了Java对象与数据表之间的映射(我明白自己在重复这句话)。在Hibernate中,虽然它操作的对象是JavaBean类(例如Department类和Employee类),但最终会转换为对数据库中表(例如,T_Department和T_Employee)的操作。
(3)在上面谈到,T_Department和T_Employee两个表存在1:N的关系,那么在Department类和Employee类的实例(instance)当中,也应该体现出这种关系。在T_Employee表中,有外键字段deptId,因此由T_Employee表对应的Employee类来维护这处关联关系是“理所当然”的。
(4)Hibernate,不只是让“理所当然”的事情实现就行了,它提供了更强大的功能,就是可以让Department类来维护T_Department和T_Employee两个表之间的关联关系。当然,Hibernate提供的这个功能是可选的,开发者可以使用,也可以不使用。这个功能以inverse属性的形式存在于Hibernate的映射文件中,它代表的是维护两表之间relationship的权限(权力):inverse="false"表示不进行反转,也就是拥有维护两表之间relationship的权力;inverse="true"表示进行反转,不具有维护两表这间relationship的权力。
(5)再说直白一些就是,当我们保存Department类的实例对象时,如果Deparment类的实例与某几个Employee类的对象之间有关联,那么Hibernate会根据inverse的值来决定是否更新T_Employee表中的deptId字段。注意:正常情况下,如果保存Employee的对象,更新T_Employee表中的deptId字段,是正常的,是无可厚非的;现在的情况是,保存Deparment类的实例对象,而对T_Employee表的deptId进行更新,它这样做的目的就是为了维护两个表之间关系。
(6)inverse属性控制的是维护两个表之间关系,而cascade属性控制的是“是否更改另一个表的内容”。在上面的例子中,inverse可能只是控制Department类是否可以更新T_Employee表的deptId字段,而cascade属性是控制是否可以在T_Employee表中添加或删除一条数据记录;inverse只影响一个字段,而cascade影响一行数据。
1、inverse属性
Inverse Attribute有时被称为Relationship Owner。
应用场景:When we are talking about one-to-many or many-to-many relationships, one of the sides should take the responsibility of managing the relationship. (当我们谈到1对多、多对多关系时,其中一方需要维护它们之间的映射关系。inverse属性的应用场景是1对多(one-to-many)或多对多(many-to-many),在多对1(many-to-one)的情形中用不到。)
默认值:Note that the inverse="false" is actually a default, so you can skip providing the inverse attribute if you wish to set the inverse relationship to false.(注意:inverse的默认值是false。)
序号 | 操作类型 | 是否影响 | 具体说明 |
---|---|---|---|
1 | 保存数据 | 有影响 |
如果设置控制反转,即inverse=true, 然后通过部门方维护关联关系。在保存部门的时候,同时保存员工,数据会保存,但关联关系不会维护。即外键字段deptId为NULL |
2 | 获取数据 | 无 | 获取数据只是参照两个表之间的关系,并不会对这种关系进行修改,因此获取数据不会受到影响。 |
3 | 解除关联关系 | 有影响 |
inverse=false, 可以解除关联 inverse=true, 当前方(部门)没有控制权,不能解除关联关系。(不会生成update语句,也不会报错) |
4 | 删除数据 | 有影响 |
inverse=false, 有控制权, 可以删除。先清空外键引用,再删除数据。 inverse=true, 没有控制权: 如果删除的记录有被外键引用,会报错,违反主外键引用约束! 如果删除的记录没有被引用,可以直接删除。 |
App_Inverse.java
package com.rk.hibernate.g_one2Many; import java.util.HashSet; import java.util.Set; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.junit.Test; public class App_Inverse { private static SessionFactory sf; static { sf = new Configuration() .configure() .addClass(Department.class) .addClass(Employee.class) .buildSessionFactory(); } //1. 是否设置inverse,对保存数据的影响? 有 //如果inverse="false",则添加Deparment和Employee之间的关联关系 //如果inverse="true",则不添加关联关系 @Test public void testSave() { int i = 0; Session session = sf.openSession(); session.beginTransaction(); // 员工1 Employee emp1 = new Employee(); emp1.setEmpName("张小三" + i); emp1.setSalary(1000); // 员工2 Employee emp2 = new Employee(); emp2.setEmpName("李小四" + i); emp2.setSalary(800); // 部门 Department dept = new Department(); dept.setDeptName("部门" + i); Set<Employee> emps = new HashSet<Employee>(); emps.add(emp1); emps.add(emp2); dept.setEmps(emps);// inverse=true, 不会设置关联关系。此时的关联应该通过员工方维护 session.save(dept); session.save(emp1); session.save(emp2); session.getTransaction().commit(); session.close(); System.out.println("执行结束!"); } //2. 是否设置inverse,对获取数据的影响? 无. @Test public void testGet() { Session session = sf.openSession(); session.beginTransaction(); Department dept = (Department) session.get(Department.class, 2); System.out.println(dept.getDeptId()); System.out.println(dept.getDeptName()); System.out.println(dept.getEmps()); session.getTransaction().commit(); session.close(); System.out.println("执行结束!"); } //3. 是否设置inverse,对关联关系的影响? 有 //如果inverse="false",则删除Deparment和Employee之间的关联关系 //如果inverse="true",则不可以删除关联关系 @Test public void testClear() { Session session = sf.openSession(); session.beginTransaction(); // 获取部门 Department dept = (Department) session.get(Department.class, 2); // 解除关系 dept.getEmps().clear(); session.getTransaction().commit(); session.close(); System.out.println("执行结束!"); } //4. 是否设置inverse,对删除的影响? 有 //如果inverse="false",则可删除Deparment和Employee之间的关联关系 //如果inverse="true",则不可以删除关联关系。 // 如果删除的记录有被外键引用,会报错,违反主外键引用约束! // 如果删除的记录没有被引用,可以直接删除。 @Test public void testDelete() { Session session = sf.openSession(); session.beginTransaction(); // 获取部门 Department dept = (Department) session.get(Department.class, 3); if (dept != null) { session.delete(dept); } session.getTransaction().commit(); session.close(); System.out.println("执行结束!"); } }
2、cascade属性
作用:When persisting object graphs, we usually have to issue save (or update) commands on individual entities. However, the cascade attribute defined on the graph lets us save the whole graph without our having to worry about saving the entities one by one. Setting the cascading behavior can reduce the lines of code and make it more succinct. We only have to save the parent node; the rest of the graph is handled by Hibernate‘s runtime.
使用范围:The cascade attribute is set on the collection attributes.
cascade 表示级联操作,它可能的值如下:
none 不级联操作, 默认值
save-update 级联保存或更新
delete 级联删除
save-update,delete 级联保存、更新、删除
all 同上。级联保存、更新、删除
App_Cascade.java
package com.rk.hibernate.g_one2Many; import java.util.HashSet; import java.util.Set; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.junit.Test; public class App_Cascade { private static SessionFactory sf; static { sf = new Configuration() .configure() .addClass(Department.class) .addClass(Employee.class) .buildSessionFactory(); } @Test public void testSave() { int i=4; Session session = sf.openSession(); session.beginTransaction(); //员工1 Employee emp1 = new Employee(); emp1.setEmpName("张小三" + i); emp1.setSalary(1000); //员工2 Employee emp2 = new Employee(); emp2.setEmpName("李小四" + i); emp2.setSalary(800); //部门 Department dept = new Department(); dept.setDeptName("部门" + i); Set<Employee> emps = new HashSet<Employee>(); emps.add(emp1); emps.add(emp2); dept.setEmps(emps); session.save(dept); //设置级联保存后,只需要保存dept对象 // session.save(emp1); // session.save(emp2); session.getTransaction().commit(); session.close(); System.out.println("执行结束!"); } @Test public void testDelete() { Session session = sf.openSession(); session.beginTransaction(); // 获取部门 Department dept = (Department) session.get(Department.class, 3); if(dept != null) { session.delete(dept);// 级联删除 } session.getTransaction().commit(); session.close(); System.out.println("执行结束!"); } }
3、hibernate常见面试题: inverse与cascade区别?
(1)谈到两者的区别,必须是两者有相近或相似之处
(2)inverse和cascade的相似之处是它们两个都出现在one-to-many和many-to-many的映射当中
(3)两者的区别是inverse是控制是否维护两表之间relationship,而cascade是控制是否进行级联操作。
(4)两者存在一个执行的前后关系:cascade在前,inverse在后,因为总是先有“数据”本身,再有“数据之间的关系”。cascade操作的是数据,而inverse操作的是关系。
Different between cascade and inverse
http://www.mkyong.com/hibernate/different-between-cascade-and-inverse/