一对多关联
首先举一例:阵型cancan的人找lulu阵型里的人做心灵pk,无奈cancan阵型里的人与lulu阵型实力相距甚远。。。于是提出cancan阵型里的人可以群K lulu阵型里的单个人,当然,接受单挑。
在hibernate的映射中,一对多关联分为单向一对多和双向一对多关联。
单向一对多
TLulu.java:
package learnHibernate.bean; import java.io.Serializable; import java.util.List; import java.util.Set; public class TLulu implements Serializable { private static final long serialVersionUID = -252962688967803016L; private int id ; private String name; private String sixthSense; private TOham oh; private Set<TCancan> cs; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public TOham getOh() { return oh; } public void setOh(TOham oh) { this.oh = oh; } public String getSixthSense() { return sixthSense; } public void setSixthSense(String sixthSense) { this.sixthSense = sixthSense; } public Set<TCancan> getCs() { return cs; } public void setCs(Set<TCancan> cs) { this.cs = cs; } }
TCancan.java
package learnHibernate.bean; public class TCancan { private int id; private String name; private String think; private TOham oh; private int rivalId; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getThink() { return think; } public void setThink(String think) { this.think = think; } public TOham getOh() { return oh; } public void setOh(TOham oh) { this.oh = oh; } public int getRivalId() { return rivalId; } public void setRivalId(int rivalId) { this.rivalId = rivalId; } }
TLulu.hbm.xml
<?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="learnHibernate.bean"> <class name="TLulu" table="t_lulu"> <id name="id" column="id" type="java.lang.Integer"> <generator class="foreign"> <param name="property">oh</param> </generator> </id> <one-to-one name="oh" class="TOham" constrained="true"/> <property name="name" column="name" type="java.lang.String" /> <property name="sixthSense" column="sixthsense" type="java.lang.String" /> <!-- 这里用set存储多个TCancan,因为是一对多的关联, set中必须指定“多”方的关联表以及关联字段 --> <set name="cs" table="t_cancan" cascade="all"> <key column="rivalid" /> <one-to-many class="TCancan" /> </set> </class> </hibernate-mapping>
TCancan.hbm.xml
<?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="learnHibernate.bean"> <class name="TCancan" table="t_cancan"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native" /> </id> <one-to-one name="oh" class="TOham" property-ref="can" /> <property name="name" column="name" type="java.lang.String" /> <property name="think" column="think" type="java.lang.String" /> <property name="rivalId" column="rivalid" type="java.lang.Integer" /> </class> </hibernate-mapping>
注意,在做保存更新操作的时候,为了保持关联关系,只能通过主控方(此处为被外键引用方:TLulu)对被动方(持有关联外键方:TCancan)进行操作。所以这里就可能出现一个问题,当关联字段不允许为null时,hibernate在进行创建或更新关联关系的时候可能出现约束违例。
现在想对某个TLulu的人再分配一个TCancan对手:
TLulu lu = (TLulu)session.get(TLulu.class, new Integer(9)); TCancan can = new TCancan(); can.setName("Can3"); can.setThink("Inversion not exists"); lu.getCs().add(can); session.save(lu); tx.commit();
此时问题出现了:当执行到session.save(lu)时,hibernate执行如下sql:
Hibernate: insert into t_cancan (name, think, rivalid) values (?, ?, ?)
于是抛了一个异常:org.hibernate.exception.ConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`hibernate/t_cancan`, CONSTRAINT `FK_t_cancan_1` FOREIGN KEY (`rivalid`) REFERENCES `t_lulu` (`id`))。
这是因为此时关联是单向的关联关系是由TLulu对象维持,而TCancan并不知自己与哪个TLulu相关联,所以在save(lu)的时候去保存Tcancan,只能想先给rivalid插个空值,其实当执行tx.commit()的时候,hibernate会执行一条update语句,因为save的时候是saveTLulu对象的,所以hibernate会将TLulu的对象自身的id赋值给TLulu的对象中的TCancan,在事务提交的时候,hibernate会发现这一变化,于是就执行一条sql语句。
针对上述,现在将TCancan.hbm.xml中 的rivalid映射属性去掉,执行save的时候就不会试图将null保存给t_cancan了,此处可以对t_cancan表做些修改,给rivalid设置默认值。这样就避免了上述异常。
但问题是为了插一条记录执行两个sql语句,效率并不高,这里完全只需一条sql能搞定,只要想法使得执行save(lu)的时候让can对象知道如何获取lu对象的id并以其作为自身的rivalId的值。
于是,双向的一对多关联来了。
双向一对多
双向的一对多关联其实是“一对多”与“多对一”关联的组合。也就是说在主控方配置一对多映射的同时,也需要在被控方配置多对一的映射。
将TCancan.hbm.xml修改如下:
<?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="learnHibernate.bean"> <class name="TCancan" table="t_cancan"> <id name="id" column="id" type="java.lang.Integer"> <generator class="native" /> </id> <one-to-one name="oh" class="TOham" property-ref="can" /> <property name="name" column="name" type="java.lang.String" /> <property name="think" column="think" type="java.lang.String" /> <many-to-one name="lu" class="TLulu" cascade="none" outer-join="auto" update="true" insert="true" access="property" column="rivalid" not-null="true" /> </class> </hibernate-mapping>
TCancan.java修改如下:
package learnHibernate.bean; public class TCancan { private int id; private String name; private String think; private TOham oh; private TLulu lu; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getThink() { return think; } public void setThink(String think) { this.think = think; } public TOham getOh() { return oh; } public void setOh(TOham oh) { this.oh = oh; } public TLulu getLu() { return lu; } public void setLu(TLulu lu) { this.lu = lu; } }
执行代码修改如下,加了一行can.setLu(lu):
TLulu lu = (TLulu)session.get(TLulu.class, new Integer(10)); TCancan can = new TCancan(); can.setName("Can3"); can.setThink("Inversion not exists"); can.setLu(lu); lu.getCs().add(can); session.save(lu); tx.commit();
结果输出:
Hibernate: insert into t_cancan (name, think, rivalid) values (?, ?, ?) Hibernate: update t_cancan set rivalid=? where id=?
。。。结果不对,不应该输出两个sql,应该是一个insert的sql才对。。。
原因是这里的关联主控方还是TLulu,在hibernate的一对多的映射关联概念中,关联关系的维护室主控方负责的,虽然执行代码中已经can.setLu(lu)这样了,但由于TCancan不是主控方,所以,在映射的层面上,TCancan不会因为有了can.setLu(lu)而主动将lu的id赋值到自身的rivalid。
于是此时需要把TCancan作为关联的主控方,有它负责维护关联关系。
对TLulu.hbm.xml的set节点做下修改:
<set name="cs" table="t_cancan" cascade="all" inverse="true"> <key column="rivalid" /> <one-to-many class="TCancan" /> </set>
inverse设置为true,就行了。意思是我TLulu本来的关联主控方放弃主控角色的责任,交由TCancan被控方负责维护关联。
重新执行代码,结果:
Hibernate: insert into t_cancan (name, think, rivalid) values (?, ?, ?)
算是成事了。
注意的是inserve与cascade的区别,inverse是指关联关系的控制方向,而cascade是层级的连锁操作。在一对多的关联中将多的一方设为主控方去维护关联,有助性能优化。