级联和关系维护
Cascade 用来说明当对主对象进行某种操作时,是否对其关联的从对象也作类似的操作,常用的cascade:none,all,save-update,delete,lock,refresh,evict,replicate,persist,merge,delete-orphan(one-to-many).一般对many-to-many,many-to-many不设置级联,在<one-to-one>和<one-to-one>中设置级联。
inverse表示“是否放弃维护关联关系 ”(在java里两个对象产生关联时,对数据库表的影响),在one-to-many和many-to-many的集合定义中使用,inverse=“true”表示该对象不维护关联关系;该属性的值一般在使用有序集合时设置成false(注意hibernate的缺省值是false )。
one-to-many维护关联关系就是更新外键。many-to-many维护关联关系就是在中间表增减记录。
注:配置成one-to-one的对象不维护关联关系。
接着上面的部门和员工的例子:
department的映射配置文件为:
<?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="hibernate.collections"> <class name="Department"> <id name="id"> <generator class="native" /> </id> <property name="name"/> <map name="emps"> <key column="depart_id" /> <map-key type="string" column="name"/> <one-to-many class="Employee" /> </map> </class> </hibernate-mapping>
程序:
static void add(){ Department depart = new Department(); depart.setName("depart"); Employee e1 = new Employee(); e1.setName("e1"); Employee e2 = new Employee(); e2.setName("e2"); Map<String, Employee> emps = new HashMap<String, Employee>(); emps.put(e1.getName(), e1); emps.put(e2.getName(), e2); depart.setEmps(emps); Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); //s.save(e1); //s.save(e2); s.save(depart); tx.commit(); }
执行以上代码,程序出现异常:严重: Could not synchronize database state with session
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: hibernate.collections.Employee
在代码中,department知道了employee.s1和s2未持久化,使得保存department时出现异常。 把配置文件做一下修改,
<map name="emps" cascade="save-update" >
<key column="depart_id" />
<map-key type="string" column="name"/>
<one-to-many class="Employee" />
</map>
再运行以上代码,就可以正确的保存s1和s2了,因为对他们进行了级联操作,保存department时,也save了employee。
级联操作,可以把上面的选项以逗号分隔开,逐一添加即可。如:
<map name="emps" cascade="save-update,delete,refresh" ></map>
但级联操作也要慎用,要根据自己具体的业务需求来进行相关配置。
还有:
在修改department.hbm.xml配置文件:
<map name="emps"> <key column="depart_id" /> <map-key type="string" column="name"/> <one-to-many class="Employee" /> </map>
执行以下代码:
static void add() { Department depart = new Department(); depart.setName("depart"); Employee e1 = new Employee(); e1.setName("e1"); e1.setDepart(depart); Employee e2 = new Employee(); e2.setName("e2"); e2.setDepart(depart); Map<String, Employee> emps = new HashMap<String, Employee>(); emps.put(e1.getName(), e1); emps.put(e2.getName(), e2); depart.setEmps(emps); Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); s.save(e1); s.save(e2); s.save(depart); tx.commit(); }
hibernate输出以下sql语句:
上面的三条插入语句很容易理解。下面的四条更新语句分别对应:
e1.setDepart(depart);
e2.setDepart(depart);
depart.setEmps(emps);
可以验证,如果把 e1.setDepart(depart); e2.setDepart(depart);这两句注释掉,产生的更新语句只有两条:
由此可见,hibernate根据程序代码的执行,采取相应的操作。其实更新语句只需做一次就够了 。对于数据库而言,在程序中,告诉部门有哪些员工,或者告诉员工,你属于那个部门。对于前者,部门需要维护关系。对于后者,员工需要维护关系。他们最终都可以在数据库表中表达出部门和员工的关系。在程序中对两者都手动设定了双方的关系,这样也可以,但显得有些冗余(做了两次更新)。因此,双方都会对关系进行维护,所以,双方都会向数据库提交设定关系的请求,这就带来了数据的重复设置,正如上面的四条更新语句。
解决问题的方案是:我们可以让其中一方放弃对关系的维护。 我们一般让一方放弃对关系的维护,让多方维护。这也比较好理解,正如现实生活中,老师不一定要记住所有学生,但学生记住老师则相对比较容易。
充:inverse不能在有序的集合中使用 。比如一般不这样配置:
<list name="emps" inverse="true"></list> 原因是,list需要维护添加的顺序,如果放弃了关系的维护,那么顺序就也不回得到hibernate的管理。这无疑是矛盾的。
只要在配置文件中做一下修改即可:
<map name="emps" inverse="true">
<key column="depart_id" />
<map-key type="string" column="name"/>
<one-to-many class="Employee" />
</map>
在执行以下代码:
Department depart = new Department(); depart.setName("depart"); Employee e1 = new Employee(); e1.setName("e1"); e1.setDepart(depart); Employee e2 = new Employee(); e2.setName("e2"); e2.setDepart(depart); Map<String, Employee> emps = new HashMap<String, Employee>(); emps.put(e1.getName(), e1); emps.put(e2.getName(), e2); depart.setEmps(emps); Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); s.save(e1); s.save(e2); s.save(depart); tx.commit();
执行的sql语句为:
分析 :inverse="true"表示放弃对关系的维护。所以上面的两条更新语句是由Employee的setDepart方法产生的。因为关系有Employee来维护。由于department放弃了关系的维护,即使在代码中做了 depart.setEmps(emps)操作,hibernate也会忽略。通过以上方法,可以有效的减少数据库的操作次数,同时不影响关系的建立。
所谓的关系维护,就是更新外键。
改进:
从以上显示的sql语句,可以看出,在插入employee数据是也是插入了depart_id的,只不过是空值,然后插入department,这时就有了depart_id了,这时就去更新employee表,把depart_id更新。其实可以先保存department,这时一开始就有depart_id了,就不会有更新语句了。
代码如下:
static void add() { Department depart = new Department(); depart.setName("depart"); Employee e1 = new Employee(); e1.setName("e1"); e1.setDepart(depart); Employee e2 = new Employee(); e2.setName("e2"); e2.setDepart(depart); Map<String, Employee> emps = new HashMap<String, Employee>(); emps.put(e1.getName(), e1); emps.put(e2.getName(), e2); depart.setEmps(emps); Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); s.save(depart); s.save(e1); s.save(e2); tx.commit(); }
输出的sql语句为:
心得: 根据程序执行过程中,打印输出的sql语句,只需冷静地分析程序的执行过程,不断地改进调优,可以有效的改善程序的性能。